Uncaught NoElError: An 'el' must be specified for a region - backbone.js

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.

Related

Accessing app object from Marionette ItemView method

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.

Object doesn't support property or method 'bind' - Backbone.js

I have build my application in Backbone.js using MVC. Everything, is running fine in Chrome/Firefox/IE 9 and above but not in IE8 and below:-
var userDetailView = backbone.View.extend({
el: '#user-details',
tagName: 'div',
template: Handlebars.templates.UserDetails,
model: userModel,
initialize: function () {
_.bindAll(this, "render");
this.model.bind('change', this.render);
return this;
}
});
I am getting error as below:-
SCRIPT438: Object doesn't support property or method 'bind'
Can anyone help?
What is this.model? How are you instantiating that view? I'd guess that this.model is not what it should be. In a view, the model property should be a model instance and you're suppose to say things like:
var m = new Model;
var v = new View({ model: m });
A model instance will have a bind method (AKA on) but, in non-stone-age browser, so will a function:
The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.
A model "class":
var M = Backbone.Model.extend({ ... });
is a function (just like anything else you can call new on in JavaScript). This means that you can say things like this in newer browsers:
var M = Backbone.Model.extend({ ... });
var v = new View({ model: M });
and this.model.bind('change', this.render) will execute inside the View, it won't call the bind you're looking for but it will call a bind, it will call Function.bind.
Start passing a model instance to your view and things should start making more sense.
Clarification: If you check the MDN Browser compatibility section on Function.bind, you'll see that there is no bind for functions in IE until IE9. So Chrome, Firefox, and IE9 all support calling bind on functions but IE8 does not; this precisely matches the observed behavior.

Rendering a closed Marionette view

Shouldn't a closed Marionette view re-delegate the defined events (events, modelEvents, CollectionEvents) when rendering again?
It seems as if I have to manually call delegateEvents after closing and re-rendering a view. Otherwise the view won't work as expected.
http://jsfiddle.net/4DCeY/
var app = new Marionette.Application();
app.addRegions({
main: '.main'
});
var MyView = Marionette.ItemView.extend({
template: _.template('Hi, I\'m a view! Foo is: <%= foo %>'),
modelEvents: {
'change': 'onChange'
},
onChange: function() {
alert('change!');
}
});
var Model = Backbone.Model.extend({});
app.addInitializer(function() {
var m = new Model({foo: 'bar'});
var myView = new MyView({
model: m
});
app.main.show(myView);
myView.close();
app.main.show(myView);
m.set({foo: 'baz'});
});
$(document).ready(function(){
app.start();
});
If I understand your question right, there are multiple open github issues about this.
For example:
https://github.com/marionettejs/backbone.marionette/pull/654
https://github.com/marionettejs/backbone.marionette/issues/622
Last time I checked, Derick (the creator of Marionette) didn't feel like reusing closed views should be something regions should do.
So you could
simply create a new view and show that one
manually call delegateEvents - but there was an issue with multiple event bindings that I can't remember right now, so be careful about that one (not at work right now, so can't take a peek at the code, sorry)
write your own region manager
or wait and see if Derick will merge one of the pull requests
a couple of points:
You do not need to call myView.close() Marionette Region will take care of that when you show another view
Marionette.Region will not replace the same view with itself. It will just skip the redundant procedure if you want to test this correctly you need 2 views
If you want a change in model to invoke render you must explicitly write it
I altered the jsfiddle with the following things:
added myView1 and myView2
removed explicit call to myView.close
added a call for this.render() from the onChange function
Here is the corrected jsfiddle http://jsfiddle.net/4DCeY/1/ :
app.addInitializer(function() {
var m = new Model({foo: 'bar'});
var myView1 = new MyView({
model: m
});
var myView2 = new MyView({
model: m
});
app.main.show(myView1);
app.main.show(myView2);
m.set({foo: 'baz'});
});
And:
onChange: function() {
alert('change!');
this.render();
}

Creating backbone views with models from other views

Background:
I am making changes to an application that uses backbone.js with Handlebars as the templating engine. After a change event fires I need to create html that is appended to the current DOM structure which is basically just a spit-out of information that is contained in the model. This change needed to fit in the already established application structure.
Issue:
I have created a new view that uses a Handlebars template and the model to create the html. I then instantiate that view and call the render function and append the output using JQuery. What I am noticing is that when the html is rendered the model that is passed in because attributes on the $el instead of filling in the template (like I think it should).
View I'm altering:
$.hart.TestView = Backbone.View.extend({
tagName: "li",
template: Handlebars.compile($('#templateOne').html()),
initialize: function () {
this.model.on('change', function () {
this.createMoreInfoHtml();
}, this);
},
selectSomething: function () {
this.$el.removeClass('policies');
this.createMoreInfoHtml(); //function created for new view stuff
},
createMoreInfoHtml: function () {
var id = this.$el.attr('data-id', this.model.get("ID"));
$('.info').each(function () {
if ($(this).parent().attr('data-id') == id
$(this).remove();
});
var view = new $.hart.NewView(this.model, Handlebars.compile($("#NewTemplate").html()));
$('h1', this.$el).after(view.render().el);
},
render: function () {
... //render logic
}
});
View I Created:
$.hart.NewView = Backbone.View.extend({
initialize: function (model, template) {
this.model = model;
this.template = template;
},
render: function () {
this.$el.html(this.template({ info: this.model }));
this.$el.addClass('.info');
return this;
}
});
Json the is the model:
{
"PetName":"Asdfasdf",
"DateOfBirth":"3/11/2011 12:00:00 AM",
"IsSpayNeutered":false,
"Sex":"F",
"SpeciesID":2,
"ID":"ac8a42d2-7fa7-e211-8ef8-000c2964b571"
}
The template
<script id="NewTemplate" type="text/html">
<span>Pet Name: </span>
<span>{{this.PetName}}</span>
</script>
So now to the question: What am I doing wrong? Why are the properties of the model being created as attributes on the $el instead of filling in the template? Can someone please direct me as to how to get the results I am looking for?
Let's skip the problem Jack noticed.
The way you're creating your view is just wrong. It may work as you get the expected arguments in the initialize function, but it has unexpected behaviors you don't see. See the View's constructor:
var View = Backbone.View = function(options) {
this.cid = _.uniqueId('view');
this._configure(options || {});
Now let's have a look at this _configure method:
_configure: function(options) {
if (this.options) options = _.extend({}, _.result(this, 'options'), options);
_.extend(this, _.pick(options, viewOptions));
And of course...
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
Ok here we are... Basically when passing the model as the options argument, you're passing an object with an attributes key (the attributes of your model). But this attributes key is also used in the View to bind attributes to its element! Therefore the behavior your noticed.
Now, other wrong thing. You're compiling your template each time you create a new function, but not using it as a singleton either. Put your template in the view:
$.hart.NewView = Backbone.View.extend({
template: Handlebars.compile($("#NewTemplate").html(),
And change your view's creation to make the whole thing work:
new $.hart.NewView({model: this.model});
Oh, and get rid of this useless initialize method. You're just doing things Backbone already does.

Handlebar compiled html not recognizing template function in backbone.js

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.

Resources