I am trying to send a "point" parameter through my "discover "route, and pass it into the corresponding 'discoverPointView' function's model selector, so that I can change the model in the view based on the parameter. Can anyone explain to me how and why this isn't working? And also, you'll notice that I'm using a lot of comparative logic in the route function...can you also explain a better way to alternate between views? Thank you!
Routes:
App.Router.Main = Backbone.Router.extend({
routes: {
//DISCOVER VIGNETTE ROUTES
'discover': 'discoverView',
'discover/:point':'discoverPointView',
},
discoverView: function() {
var contentDiscover = document.getElementById('discoverWrapper');
this.hidePages();
contentDiscover.classList.add('active');
var discoverView = new AllDiscoverPointsView({collection: allDiscoverPoints});
$("#discoverWrapper").html(discoverView.render().el);
//$(".point").removeClass("active");
//$("#" + point).addClass("active");
},
discoverPointView: function(point) {
var contentDiscover = document.getElementById('discoverWrapper');
this.hidePages();
contentDiscover.classList.add('active');
var discoverView = new AllDiscoverPointsView({collection: allDiscoverPoints});
$("#discoverWrapper").html(discoverView.render().el);
if(point == "glasselephant"){
var singleDiscoverPointView = new SingleDiscoverPointView({ model: point });
$("#discoverWrapper").append(singleDiscoverPointView.render().el);
$(".subpage").removeClass("active");
$(singleDiscoverPointView.el).addClass("active");
}
if(point == "carvedstatue"){
var singleDiscoverPointView = new SingleDiscoverPointView({ model: point });
$("#discoverWrapper").append(singleDiscoverPointView.render().el);
$(".subpage").removeClass("active");
$(singleDiscoverPointView.el).addClass("active");
}
},
views:
// FULL DISCOVER POINT COLLECTION VIEW
var AllDiscoverPointsView = Backbone.View.extend({
tagName: 'ul',
render: function() {
this.collection.each(function(model) {
var discoverPointView = new DiscoverPointView({ model: model });
this.$el.append(discoverPointView.render().el);
}, this);
return this;
}
});
// SINGLE DISCOVER POINT VIEW
var DiscoverPointView = Backbone.View.extend({
tagName:'li',
className:'point',
events: {
"click": "select"
},
select: function(){
$('.point').removeClass('active');
this.$el.addClass('active');
},
template: _.template(template('discoverViewTemplate')),
render: function(){
this.id = this.model.get('idWord');
this.$el.attr('id',this.id).html(this.template(this.model.toJSON()));
return this;
}
});
// SINGLE DISCOVER POINT VIEW
var SingleDiscoverPointView = Backbone.View.extend({
tagName: 'div',
className:'subpage',
template: _.template(template('singleDiscoverViewTemplate')),
render: function(){
this.id = this.model.get('idWord');
this.$el.attr('id',this.id).html(this.template(this.model.toJSON()));
return this;
}
});
models/collections:
// COLLECTION OF ALL POINTS IN DISCOVER
var AllDiscoverPoints = Backbone.Collection.extend({
model: DiscoverPoint
});
// MODEL OF A SINGLE DISCOVER POINT
var DiscoverPoint = Backbone.Model.extend({
defaults: {
title: 'Glass Elephant',
imgPath: 'root/public/img/glass-elephant.png',
caption: 'The Imagists were extreme collectors, scouring countless thrift shops and beyond!',
link: 'discover/glasselephant',
misc: 'Any extra info goes here...'
}
});
// DISCOVER POINT MODEL INSTANCES
var glasselephant = new DiscoverPoint({
id: 1,
idWord: 'glasselephant',
title: 'Glass Elephant',
imgPath: 'root/public/img/glass-elephant.png',
caption: 'The Imagists were extreme collectors, scouring countless thrift shops and beyond!',
link: 'discover/glasselephant',
misc: 'Any extra info goes here...'
});
var carvedstatue = new DiscoverPoint({
id: 2,
idWord: 'carvedstatue',
title: 'Carved Statue',
imgPath: 'root/public/img/carved-statue.png',
caption: 'The Imagists were extreme collectors, scouring countless thrift shops and beyond!',
link: 'discover/carvedstatue',
misc: 'Any extra info goes here...'
});
Related
I wonder if someone can help to find what's wrong in this case. I get "Uncaught ReferenceError: text is not defined" in line 6 app.js:
((__t=( text ))==null?'':_.escape(__t))+
driver.js:
var Marionette = require('backbone.marionette');
var TodoView = require('./views/layout');
var initialData = {
items: [
{assignee: 'Scott', text: 'Write a book about Marionette'},
{assignee: 'Andrew', text: 'Do some coding'}
]
};
var App = new Marionette.Application({
onStart: function(options) {
var todo = new TodoView({
collection: new Backbone.Collection(options.initialData.items),
model: new ToDoModel()
});
todo.render();
todo.triggerMethod('show');
}
});
App.start({initialData: initialData});
views/layout.js
var Backbone = require('backbone');
var Marionette = require('backbone.marionette');
var ToDoModel = require('../models/todo');
var FormView = require('./form');
var ListView = require('./list');
var Layout = Marionette.View.extend({
el: '#app-hook',
template: require('../templates/layout.html'),
regions: {
form: '.form',
list: '.list'
},
collectionEvents: {
add: 'itemAdded'
},
onShow: function() {
var formView = new FormView({model: this.model});
var listView = new ListView({collection: this.collection});
this.showChildView('form', formView);
this.showChildView('list', listView);
},
onChildviewAddTodoItem: function(child) {
this.model.set({
assignee: child.ui.assignee.val(),
text: child.ui.text.val()
}, {validate: true});
var items = this.model.pick('assignee', 'text');
this.collection.add(items);
},
itemAdded: function() {
this.model.set({
assignee: '',
text: ''
});
}
});
module.exports = Layout;
todoitem.html
<%- item.text %> — <%- item.assignee %>
Any can me explain why text is not defined?
check your ToDoModel for a typo, the Backbone Model field should be "defaults" not "default", while parsing for a template Marionette view looks for "defaults" field:
https://marionettejs.com/docs/master/template.html#rendering-a-model
so the ToDoModel code should go like this:
...
var ToDo = Backbone.Model.extend({
defaults: {
assignee: '',
text: ''
},
...
You should take a look at the Marionnette's ItemView documentation which explain how to render a template with custom data.
var my_template_html = '<div><%= args.name %></div>'
var MyView = Marionette.ItemView.extend({
template : function(serialized_model) {
var name = serialized_model.name;
return _.template(my_template_html)({
name : name,
some_custom_attribute : some_custom_key
});
}
});
new MyView().render();
Note that using a template function allows passing custom arguments
into the .template function and allows for more control over how the
.template function is called.
With the code you provided at the moment, I can't help.
Marionette calls 'serializeModel' before passing the context to 'template'. So, if you have a backbone.model like
{
.
.
.
attributes: {
text: 'someText',
asignee: 'someAsignee'
}
.
.
}
your template will receive
{
text: 'someText',
assignee: 'someAsignee'
}
I have worked with handlebars but not underscore exactly. There {{this.text}} and {{this.assignee}} works like a charm in the template. So, try this.text or text in place of item.text, see if that works
I am relatively new to Backbone.js and having difficulty rendering a subView. I have subViews in other parts of the app working properly, but I cant even render simple text in this one.
View:
Feeduni.Views.UnifeedShow = Backbone.View.extend({
template: JST['unifeeds/show'],
tagName: "section",
className: "unifeed-show",
render: function() {
var content = this.template({ unifeed: this.model });
this.$el.html(content);
var subView;
var that = this;
this.model.stories().each(function(stories) {
subView = new Feeduni.Views.StoriesShow({ model: stories });
that.subViews.push(subView);
that.$el.find(".show-content").append(subView.render().$el);
});
return this;
},
});
Subview:
Feeduni.Views.StoriesShow = Backbone.View.extend({
template: JST['stories/show'],
tagName: "div",
className: 'stories-show',
render: function() {
this.$el.text("Nothing shows up here");
return this;
},
});
Model:
Feeduni.Models.Unifeed = Backbone.Model.extend({
urlRoot: "/api/uninews",
stories: function() {
this._stories = this._stories || new Feeduni.Subsets.StoriesSub([], {
parentCollection: Feeduni.all_unifeeds
});
return this._stories;
},
});
The text "Nothing shows up here" should be displaying in the "show content" element, but all I get is this:
<section class="unifeed-show">
<article class="show-content">
</article>
</section>
Below is a slight modification of your code showing a working main view managing some sub-views.
var UnifeedShow = Backbone.View.extend({
// I've hard-coded the template here just for a sample
template: _.template("Feed: <%= feedName %><br/> <ul class='show-content'></ul>"),
className: "unifeed-show",
initialize: function () {
// Create an array to store our sub-views
this.subViews = [];
},
render: function () {
var content = this.template(this.model.toJSON());
this.$el.html(content);
var subView;
var that = this;
var subViewContent = this.$el.find(".show-content");
this.model.stories().each(function (story) {
var subView = new StoryShow({
model: story
});
this.subViews.push(subView);
subViewContent.append(subView.render().$el);
}, this);
return this;
}
});
var StoryShow = Backbone.View.extend({
tagName: 'li',
// This template will show the title
template: _.template('Title: <%= title %>'),
className: 'stories-show',
render: function () {
var content = this.template(this.model.toJSON());
this.$el.html(content);
return this;
},
});
var Unifeed = Backbone.Model.extend({
stories: function () {
// I'm just returning the value set on this model as a collection;
// You may need to do something different.
return new Backbone.Collection(this.get('stories'));
}
});
// ================================
// Code below is creating the model & view, then rendering
// ================================
// Create our model
var feed = new Unifeed();
// Put some data in the model so we have something to show
feed.set('feedName', 'A Sample Feed');
feed.set('stories', [{
title: "Story #1",
id: 1
}, {
title: "Story #2",
id: 5
}]);
// Create our main view
var mainView = new UnifeedShow({
model: feed,
el: $('#main')
});
// Render it, which should render the sub-views
mainView.render();
Here's a working JSFiddle:
https://jsfiddle.net/pwagener/7o9k5d6j/7/
Note that while this manual sort of sub-view management works OK, you'll be better off using something like a Marionette LayoutView to help manage parent and sub-views. It builds good best practices for this sort of thing without you needing to do it yourself.
Have fun!
The subview is named Feeduni.Views.StoriesShow but in your main view you are instantiating new Feeduni.Views.StoryShow. Name them consistently and see if you still have problems.
with help of books and tutorials im trying to do my own Todo list app. My problem is that when you add ah todo my model should validate if the input come with some text before rendering. i created the validate methis but it doesnt work and i dont know how to track the error, i'll appreciate if you guys could help me finding my error
Model:
var Task = Backbone.Model.extend({
validate: function(attrs){
if(!attrs.title){
return "The task has no title";
}
}
});
var task = new Task;
Collection:
var Tasks = Backbone.Collection.extend({
model: Task
});
var tasks = new Tasks([
{
title: 'my task'
},
{
title: 'my task 1'
},
{
title: 'my task 2'
},
{
title: 'my task 3'
}
]);
Views:
var TaskView = Backbone.View.extend({
tagName: "li",
template: _.template( $('#task').html() ),
initialize: function(){
this.model.on("change", this.render, this);
},
render: function(){
var template = this.template( this.model.toJSON() );
this.$el.html( template );
return this;
},
events: {
'click .icon-checkbox': 'toggleState'
},
toggleState: function(e){
var $checkbox = $(e.target);
$checkbox.toggleClass('icon-checkbox-unchecked');
}
});
var TasksView = Backbone.View.extend({
el: '#tasks',
initialize: function(){
this.render();
this.collection.on('add', this.addOne, this);
},
render: function(){
this.collection.each(this.addOne, this);
return this;
},
addOne: function(task){
var taskView = new TaskView({ model: task });
this.$el.append( taskView.render().el );
}
});
var AddTask = Backbone.View.extend({
el: '#todos',
events:{
'click #add': 'addTask'
},
addTask: function(){
var taskTitle = $('#inputTask').val();
$('#inputTask').val("");
var task = new Task( {title: taskTitle} ); // create the task model
this.collection.add(task); //add the model to the collection
}
});
var tasksView = new TasksView( {collection: tasks} );
var addTask = new AddTask( {collection: tasks} );
Edit:
I just realized that the validate methos its on ly called when you set or save info in the model, but actually what i want to do is check when the user submit a todo that if it comes with text or ini order to create and render the model.
You never actually call the validate() method. Have you looked into the Backbone Validation plugin? Works pretty slick.
https://github.com/thedersen/backbone.validation
So i came up with this solution, I am not sure if this is a good practice or not or it the view should to this but what i did is: Once you push on the add task button the addTask methos first check if the input has text or not, if it doesnt have text it just only change the placeholder to: "Todo's can not be empty" and doesnt create a model and render its view. But, if the input has text it creates the model append it to the colelction and render the view!.
Here is the new code for the view, i hope someone could tellme if this is good or if the model should validate this or any other object should to this labor.
Code:
var AddTask = Backbone.View.extend({
el: '#todos',
events:{
'click #add': 'addTask'
},
addTask: function(){
var taskTitle = $('#inputTask').val();
$('#inputTask').val(""); //clear the input
if($.trim(taskTitle) === ''){
this.displayMessage("Todo's can not be empty");
}else{
var task = new Task( {title: taskTitle} ); // create the task model
this.collection.add(task); //add the model to the collection
}
},
displayMessage: function(msg){
$('#inputTask').attr("placeholder", msg);
}
});
I'm struggling to get a simple master-detail scenario working with Backbone. Here's the jsfiddle and code is below.
Problem 1: this navigation doesn't work at all if I switch "pushstate" to true. What I really want is to have no hashes/pound signs in my urls.
Problem 2: my users might rock up on a url like /accommodation/287, not always on the home page. How would you deal with that using the router?
Thanks a lot for any help!
var AccommodationItem = Backbone.Model.extend({
defaults: {
html: "",
loaded: false
},
urlRoot: "/Home/Accommodation/"
});
var AccommodationItemView = Backbone.View.extend({
tagName: "li",
template: _.template("<a href='#accommodation/<%= id %>'><%= description %></a>"),
render: function () {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
var AccommodationList = Backbone.Collection.extend({
model: AccommodationItem
});
var DetailView = Backbone.View.extend({
initialize: function () { },
render: function () {
this.$el.html(this.model.get("html"));
},
setModel: function (model) {
this.model = model;
var $this = this;
if (!this.model.get("loaded")) {
/*
this.model.fetch({ success: function () {
$this.model.set("loaded", true);
$this.render();
}
});*/
$this.model.set("html", "<h2>Full item " + this.model.get("id") + "</h2>");
$this.model.set("loaded", true);
$this.render();
} else {
$this.render();
}
}
});
var AccommodationListView = Backbone.View.extend({
tagName: "ul",
initialize: function () {
this.collection.on("reset", this.render, this);
},
render: function () {
this.addAll();
},
addOne: function (item) {
var itemView = new AccommodationItemView({ model: item });
this.$el.append(itemView.render().el);
},
addAll: function () {
this.collection.forEach(this.addOne, this);
}
});
var App = new (Backbone.Router.extend({
routes: {
"": "index",
"accommodation/:id": "show"
},
initialize: function () {
this.detailView = new DetailView({ model: new AccommodationItem({ id: 1 }) });
$("#detail").append(this.detailView.el);
this.accommodationList = new AccommodationList();
this.accommodationListView = new AccommodationListView({ collection: this.accommodationList });
$("#app").append(this.accommodationListView.el);
},
start: function () {
Backbone.history.start({ pushState: false });
},
index: function () {
this.fetchCollections();
},
show: function (id) {
var model = this.accommodationList.get(id);
this.detailView.setModel(model);
},
fetchCollections: function () {
var items = [{ id: 1, description: "item one" }, { id: 2, description: "item two" }, { id: 3, description: "item three" }];
this.accommodationList.reset(items);
}
}));
$(function () {
App.start();
});
EDIT: In a comment below I mentioned the Codeschool backbone.js tutorial. Just want to say that I have now finished BOTH parts of the course and it DOES cover exactly the AppView pattern described in the accepted answer. It's an excellent course and I thoroughly recommend it.
you have a few of the concepts mixed up.
There is too much to explain here, so I've (very roughly) put together a patch of your code that works as you intend. I would advise that you put it side-by-side with your own and see what I have done differently.
http://jsfiddle.net/wtxK8/2
A couple of things, you should not init Backbone.history from within a router. your 'init' should look something more like this
$(function () {
window.app = new App();
window.appView = new AppView({el:document});
Backbone.history.start({ pushState: true });
});
This is setting a 'wrapper' view than encompasses the entire page. Also, you have far too much logic in your router. Try to only use the router for routes. After my quick re factor, your router only contains this:
var App = Backbone.Router.extend({
routes: {
"": "index",
"accommodation/:id": "show"
},
show: function (id) {
var model = window.appView.accommodationList.get(id);
window.appView.detailView.setModel(model);
}
});
The AppView (that I have written for you now does all of that initialize work.
var AppView = Backbone.View.extend({
initialize : function(){
this.detailView = new DetailView({ model: new AccommodationItem({ id: 1 }) });
$("#detail").append(this.detailView.el);
this.accommodationList = new AccommodationList();
this.accommodationListView = new AccommodationListView({ collection: this.accommodationList });
$("#app").append(this.accommodationListView.el);
this.fetchCollections();
},
fetchCollections: function () {
var items = [
{ id: 1, description: "item one" },
{ id: 2, description: "item two" },
{ id: 3, description: "item three" }
];
this.accommodationList.reset(items);
}
});
Even after my re factor, it's still far from optimal, but I have provided it all to help you on your journey of learning :)
I would then recommend you follow some of the on-line tutorials step-by-step so that you can set up the structure of your app in a better way.
Good Luck, and be sure to check out http://jsfiddle.net/wtxK8/2 to see it working.
EDIT: I have not address your second question. there is enough to be worked on with question 1 to keep you busy. If I have more time later, I will help further.
I have some editor application with few switchable pages, each of this pages hase it's own action in router. Views are designed using Backbone.LayoutManager. After rendering one of them (editor_publish) i need to draw google map.
When i render this page first time (withing one session), $('.map') selector returns non-empty result and then i can draw map in it. But when I'm trying to render it in second time (i.e. go to another page and then back again to this one), same selector in same code return - 'undefined'. For me this mean that template elements are not in the DOM at the moment i'm trying g to select map container. But why this happens? I can't see difference between first and second pages visit.
I remember something like that was taking place when I re-rendered same Layout manager. But in this code, new LM will be created for each router's action. How this can be solved?
The router:
Editor = Backbone.Router.extend({
immo: {},
lm: {},
nav: [],
pages: [],
routes: {
'editor/basic': 'basic',
'editor/basic/:id': 'basic',
'editor/details': 'details',
'editor/details/:id': 'details',
'editor/photos': 'photos',
'editor/photos/:id': 'photos',
'editor/publish': 'publish',
'editor/publish/:id': 'publish',},
initialize: function(){
var self = this;
if (!_length(self.immo)){
self.immo = new Immo_Object();
}
},
basic: function(id){
var self = this;
self.init(new View_EditorBasic({model: self.immo}));
self.nav.push({caption: '> Next Step', link: 'details'});
self.run(id);
},
details: function(id){
var self = this;
self.init(new View_EditorDetails({model: self.immo}));
self.nav.push({caption: '< Prev Step', link: 'basic'});
self.nav.push({caption: '> Next Step', link: 'photos'});
self.run(id);
},
photos: function(id){
var self = this;
self.init(new View_EditorPhotos({model: self.immo}));
self.nav.push({caption: '< Prev Step', link: 'details'});
self.nav.push({caption: '> Next Step', link: 'publish'});
self.run(id);
},
publish: function(id){
var self = this;
self.init(new View_EditorPublish({model: self.immo}));
self.nav.push({caption: '< Prev Step', link: 'photos'});
self.run(id);
},
init: function(page){
var self = this;
self.nav = [];
self.pages = [
{name: 'basic', caption: 'Basic Infos'},
{name: 'details', caption: 'Details'},
{name: 'photos', caption: 'Photos'},
{name: 'publish', caption: 'Publish'},
];
self.lm = new Backbone.LayoutManager({
template: 'editor',
views:{
'.editor': new View_Editor({
collection: self.pages,
views: {
'.editor_form': page,
'.editor_top_nav, .editor_bottom_nav': new View_EditorNavigation({collection : self.nav})
}})
}
});
},
done: function(self){
if (!self) self=this;
self.compose_navigation();
self.lm.render(function(el){
$('#content').html(el);
$(el).find(".facts dl").columnize();
});
},
run: function(id){
var self = this;
if (id && !self.immo.get('id')){
self.immo.set({id: id});
self.immo.fetch({success: function(){
return self.done(self);
}});
}else{
self.store();
self.done();
}
},
compose_navigation: function(){
var self = this;
_.each(self.nav, function(item){
item.link = 'editor/' + item.link;
if (self.immo.get('id')) item.link += '/' + self.immo.get('id');
});
},
store: function(){
var self = this;
var form = $('.editor_form form').serializeObject();
self.immo.set(form);
return true;
},
});
The view where problem is:
View_EditorPublish = Backbone.View.extend({
template: 'details',
serialize: function(){
return {immo: this.model, details: Immo.details(this.model)};
},
render: function(layout){
return layout(this).render().then(function(el) {
$(el).find(".facts dl").columnize();
console.log($('.map'));
});
},
});