I've been working off of Brian Mann's BackboneRails screencast, so my application structure is totally consistent with those.
The following defines the 'show' portion of a the HomepageApp Marionette Appication.
My.module('HomepageApp.Show', function(Show, App, Backbone, Marionette, $, _){
Show.Photo = Marionette.ItemView.extend({
tagName:'span'
});
Show.Photos = Marionette.CollectionView.extend({
template: 'homepage/show/templates/photos',
itemView:Show.Photo,
itemViewContainer: '#photos'
});
Show.Layout = Marionette.Layout.extend({
template: 'homepage/show/templates/layout',
regions:{
photoRegion: '#photo-region'
}
});
});
I'm also overriding the Marionette.Renderer.render function with the following:
Backbone.Marionette.Renderer.render = function(template, data){
var path = JST["backbone/apps/" + template];
try{
if(!path) throw({message: "Template '" + template + "' not found!"});
return path(data);
}
catch(err){
console.log(err.message);
}
}
All of my views, including many not shown here are working perfectly. The issue is that the 'template' propery of the Show.Photos CollectionView is turning up as 'undefined' in my renderer override giving me the following error:
Template 'undefined' not found!
The odd thing is that this even happens when I pass no value for the template property at all.
I also know that I'm passing it a valid Backbone collection on instantiation.
I'm totally stuck. Anyone familiar with this phenomenon?
A CollectionView is not supposed to have a template, for this purpose you should use CompositeView, as stated in the docs:
A CompositeView extends from CollectionView to be used as a composite
view for scenarios where it should represent both a branch and leaf in
a tree structure, or for scenarios where a collection needs to be
rendered within a wrapper template.
You can read more here: https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.compositeview.md
Related
In the following code, I am trying to trigger an event using dynamic require. For some reason I am not able to access app object in the eventRouter method. I am getting "TypeError: app is undefined" error. I have implemented listener on show event in respective controller files.
My question is similar to this post except my listeners are in different controller files and I am not able to access app object as suggested in the post.
Help appreciated !!!!
define(["app",
"tpl!templates/nav/nav.tpl",
"tpl!templates/nav/navMenuItem.tpl",
"entities/navEntity"
],
function(app, navTpl, navMenuItem, navEntity){
navMenuItem = Backbone.Marionette.ItemView.extend({
template: navMenuItem,
events: {
"click a": "eventRouter"
},
eventRouter:function(ev)
{
var that = this;
var moduleName = $(ev.currentTarget).text().toLowerCase();
require(['controllers/' + moduleName + 'Controller'], function(controller){
app.trigger(moduleName + ':show');
});
}
});
navMenu = Backbone.Marionette.CollectionView.extend({
tagName: 'ul',
itemView: navMenuItem,
collection: navEntity.navItems,
});
return {
navMenu: navMenu,
navMenuItem: navMenuItem
}
});
To overcome Circular dependencies you can check the Following :
https://stackoverflow.com/a/4881496/2303999
Manage your modules accordingly and avoid dependencies. Make common js file for functions you use use now and then. You can even use Marionette Vent object to pass events and do according on that event.
I'm trying to use this code for view animation and calling it BaseView:
https://gist.github.com/brian-mann/3947145
then extending view like this:
define(['underscore',
'handlebars',
'views/BaseView',
'text!templates/components/login.tmpl'
], function (
_,
Handlebars,
BaseView,
loginTemplate
) {
'use strict';
var LoginView, errorMap;
LoginView = BaseView.extend({
compiledTemplate: Handlebars.compile(loginTemplate),
events: {
'submit #loginForm': 'login'
},
initialize : function(options){
this.proxyLoginSuccess = options.loginSuccess;
this.errorMap = options.errorMap;
}...
});
return LoginView;
});
It is giving me this error: Uncaught NoElError: An 'el' must be specified for a region.
I tried to remove this.ensureEl(); but doesn't make any difference. Appreciate any help.
You seem to be unclear about some Marionette concepts. The code you linked isn't a view, it's a Marionette Region, and is therefore used to show views, not to be extended from as in your code. This is how you would use it (e.g.):
myApp.addRegions({
fadeRegion: FadeTransitionRegion.extend({
el: "#some-selector"
})
});
Then, you instantiate a view instance and show it:
var myView = new LoginView({
el: "#another-selector"
});
myApp.fadeRegion.show(myView);
In any case, your view needs to have an el attribute defined, either in the view definition, or when it gets instantiated (as above).
If you're still confused about the attributes and specifying them in the view definition or at run time, I'd suggest you read the free preview to my Marionette book where it's explained in more detail.
I have a very simple page that shows a collection in a table. Above it theres a search field where the user enters the first name of users.
When the user types I want to filter the list down.
Edit: I have updated the code to show how the current compositeView works. My aim is to integrate a searchView that can _.filter the collection and hopefully just update the collection table.
define([
'marionette',
'text!app/views/templates/user/list.html',
'app/collections/users',
'app/views/user/row'
],
function (Marionette, Template, Users, User) {
"use strict"
return Backbone.Marionette.CompositeView.extend({
template: Template,
itemView: User,
itemViewContainer: "tbody",
initialize: function() {
this.collection = new Users()
this.collection.fetch()
}
})
})
Divide your template in a few small templates, this increases performance at the client side, you don't have problems with overriden form elements and you have more reuseable code.
But be aware of too much separation, cause more templates means more views and more code/logic.
You don't seem to be making use of CollectionView as well as you could be. If I were you I would separate the concerns between the search box and the search results. Have them as separate views so that when one needs to rerender, it doesn't effect the other.
This code probably won't work straight away as I haven't tested it. But hopefully it gives you some clue as to what ItemView, CollectionView, and Layout are and how they can help you remove some of that boiler plate code
//one of these will be rendered out for each search result.
var SearchResult = Backbone.Marionette.ItemView.extend({
template: "#someTemplateRepresentingEachSearchResult"
)};
//This collectionview will render out a SearchResult for every model in it's collection
var SearchResultsView = Backbone.Marionette.CollectionView.extend{
itemView: SearchResult
});
//This layout will set everything up
var SearchWindow = Backbone.Marionette.Layout.extend({
template: "#someTemplateWithASearchBoxAndEmptyResultsRegionContainer",
regions:{
resultsRegion: "#resultsRegion"
},
initialize: function(){
this.foundUsers = new Users();
this.allUsers = new Users();
this.allUsers.fetch({
//snip...
});
events: {
'keyup #search-users-entry': 'onSearchUsers'
},
onSearchUsers: function(e){
var searchTerm = ($(e.currentTarget).val()).toLowerCase()
var results = this.allUsers.filter(function(user){
var firstName = user.attributes.firstname.toLowerCase();
return firstName.match(new RegExp(searchTerm))
});
this.foundUsers.set(results); //the collectionview will update with the collection
},
onRender: function(){
this.resultsRegion.show(new SearchResultsView({
collection: this.foundUsers
});
}
});
I think the most important thing for you to take note of is how CollectionView leverages the Backbone.Collection that you provide it. CollectionView will render out an itemView (of the class/type you give it) for each model that is in it's collection. If the Collection changes then the CollectionView will also change. You will notice that in the method onSearchUsers all you need to do is update that collection (using set). The CollectionView will be listening to that collection and update itself accordingly
Here is my error:
Uncaught NoTemplateError: Could not find template: '<!-- HTML Template -->
<div id="start_div">
<h2>Choose your path to ... // the rest of the template
It is telling me that there is no template but then it outputs the template that it said it could not find.
Here is my code:
require(["jquery", "marionette", "views/StartView" ],
function($, marionette, StartView) {
var SCApp = new marionette.Application();
SCApp.addRegions({
mainRegion: "#center_court"
});
var startView = new StartView();
SCApp.mainRegion.show(startView);
SCApp.start();
}
Here is the StartView.js
define(["jquery", "marionette", "text!templates/startDiv.html"],
function($, marionette, template){
var StartView = marionette.ItemView.extend({
//template: "#start_div"
template: template
});
// Returns the View class
return StartView;
});
Can anyone see what I'm doing wrong? Do I need something for templating in the require method?
Any suggestion are greatly appreciated.
Andrew
Usually Marionette search for a template inside the DOM with an ID equal to the one you reference in your view, so you have to change the loadTemplate from Marionette.TemplateCache in this way:
Backbone.Marionette.TemplateCache.prototype.loadTemplate = function(templateId) {
var template = templateId;
if (!template || template.length === 0){
var msg = "Could not find template: '" + templateId + "'";
var err = new Error(msg);
err.name = "NoTemplateError";
throw err;
}
return template;
};
I actually don't remember where I found this function, I can't find it anymore in Marionette's Wiki, anyway it's working fine for me.
I had the same issue yesterday and found the next interesting facts:
When I've changed the 'not found' template's content, it wasn't changed in error message.
When I've changed it's file name (and updated it in import statement) — the error was fixed, updated content was shown.
... then I've changed the name back, everything was fine.
Looks like some bug with caching.
Upd: here I found deep analysis and solution:
http://blog.icanmakethiswork.io/2014/03/caching-and-cache-busting-with-requirejs.html
The project I am on is currently using Backbone.js to create a website and is using Handlebars (http://handlebarsjs.com/) as the templating system. I am attempting to create a sub-view that gets values from a json document into a corresponding template and then return that to a parent view.
The problem I am running into is that when I use
Handlebars.Compile(referenceViewTemplate)
it then doesn't recognize the template function when I try to replace the tokens using
this.template({ identifier: value })
The template code is:
<div id="reference-template">
<div class="id">{{id}}</div>
<div class="reference">{{content}}</div>
</div>
The backbone model is:
define(['underscore','backbone'],
function(_, Backbone){
var reference = Backbone.Model.extend({
initialize: function(){}
});
return reference;
});
The backbone collection code is:
define(['underscore','backbone','models/reference'],
function(_, Backbone, Reference){
var References = Backbone.Collection.extend({
model: Reference,
parse:function(response){ return response; }
});
return new References;
});
The code in the parent view which calls the reference view is:
this.ref = new ReferenceView();
this.ref.model = this.model.page_refs; //page_refs is the section in the json which has the relevant content
this.ref.render(section); //section is the specific part of the json which should be rendered in the view
And the code in the ReferenceView is:
define([
// These are path alias that we configured in our bootstrap
'jquery','underscore','backbone','handlebars',
'models/reference','collections/references','text!templates/reference.html'],
function($, _, Backbone, Handlebars, Reference, References, referenceViewTemplate) {
var ReferenceView = Backbone.View.extend({
//Define the default template
template: Handlebars.Compiler(referenceViewTemplate),
el: ".overlay-references",
model: new Reference,
events:{},
initialize : function() {
this.model.bind('change', this.render, this);
return this;
},
// Render function
render : function(section) {
//this is where it says "TypeError: this.template is not a function"
$(this.el).append(this.template(References.get(section).get("content")));
return this;
}
});
I know this is a lot to read through and I appreciate anyone taking the time to do so, please let me know if there is anything else I can provide to clarify.
The answer is that apparently I was using the wrong function to compile the html. For some reason I typed in Handlebars.Compiler instead of Handlebars.compile
This hasn't solved all the problems in my project (template is being passed back now, but without the values entered), but at least it's a step forward.