I am writing a backbone (with require) application and need to search through a collection to pull out the first model (I'm using a unique id so there will only be one).
The issue I'm having is that I'm getting an error:
Uncaught TypeError: Object [object Object] has no method 'findWhere'
When it get to the line with the findWhere command.
The view initialization is:
initialize: function (models) {
this.locationCollection = new LocationCollection();
this.locationCollection.fetch();
this.render();
},
I then access the locationCollection later in another method, the first line of the method is where the error occurs:
createCrate: function (eventname) {
var setLocationList = this.locationCollection.findWhere({ Name: $('#location').val() });
console.log($('#location').val());
console.log(setLocationList);
},
Here is the declaration code the LocationCollection:
define([
'jquery',
'underscore',
'backbone',
'model/LocationModel'
], function ($, _, Backbone, LocationModel) {
var LocationCollection = Backbone.Collection.extend({
model: LocationModel,
url: "/api/locations"
});
return LocationCollection;
});
I can access the items in this.localCollection elsewhere in the view and output them into a backbone typeahead extension, so the collection has been populated.
Any idea why this collection cannot call findWhere?
_.findWhere was introduced in Underscore 1.4.4
and Backbone proxied it in Backbone 1.0
Make sure you have the adequate versions and your error should go away.
Related
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 am building my first Backbone app with similar structure to this Todo MVC example with Require.js and also using Backbone LocalStorage. Problem is when I run TweetsCollection.fetch() in HomeView, firebug gives me error: TypeError: options is undefined var method = options.update ? 'update' : 'reset';
TweetsCollection:
define([
'underscore',
'backbone',
'backboneLocalStorage',
'models/TweetModel'
], function(_, Backbone, Store, TweetModel) {
'use strict';
var TweetsCollection = Backbone.Collection.extend({
model: TweetModel,
localStorage: new Store('tweets-storage'),
initialize: function() {
console.log('Collection init...');
}
});
return new TweetsCollection();
});
HomeView init:
initialize: function() {
this.listenTo(TweetsCollection, 'add', this.addOne);
this.listenTo(TweetsCollection, 'reset', this.addAll);
this.listenTo(TweetsCollection, 'all', this.render);
TweetsCollection.fetch(); // <- Error here
},
I try to follow the example above, but I'm really lost with this.
The line of code where the error occurs is in Backbone's success callback that gets executed by Backbone.sync. Here's what that method looks like in Backbone 0.9.10:
options.success = function(collection, resp, options) {
var method = options.update ? 'update' : 'reset';
collection[method](resp, options);
if (success) success(collection, resp, options);
};
Prior to version 0.9.10, Backbone callback signature was:
options.success = function(resp, status, xhr) { ...
The Backbone.localStorage plugin, which you are evidently using, executes the callback method as follows (line 146):
if (options && options.success)
options.success(resp);
As you can see, it doesn't pass the arguments in correct order, and is missing the options argument altogether, which is where you are seeing the error.
So it would seem that the Backbone.localStorage plugin is currently incompatible with the newest Backbone version.
Edit: I went to report this issue to the author of the localStorage plugin, but looks like there is already a GitHub issue and pull request to fix this. It's not merged yet, so in the meantime you can use phoey's fork or downgrade to Backbone 0.9.9
Update: Problems solved, case closed.
I'm still having problems getting one part of my code to work.
My view now listens to the collection for updates, and what should happen is:
ListView listens to Results Collection
Results are synced
ListView creates an ItemView for each Result
ListView (ul) appends each ItemView (li)
Everything seems to work fine, up until the final step.
The function in ListView that is supposed to add the results to a list does not have access to the ListView's element.
I can create an ItemView, and retrieve it's element "<li>", but the ListView's "<ul>" cannot be referred to within the function.
Sample code bits from ListView:
el: $('.result-list'),
initialize: function() {
this.listenTo(this.collection, 'add', this.addOne);
},
addOne: function(result) {
view = new ItemView({ model: result });
this.$el.append(view.render().el);
},
In the above code, the variable view exists, as does it's element, but "this" doesn't refer to the ListView anymore.
Problem below solved
What I'm trying to accomplish is having a View module (search) able to trigger an event in a Collection (results).
When the Search View is submitted, it should pass the input field to the Collection's fetch method to retrieve results from the server. Currently, I can trigger a function from the View, but the function does not have access to any of the Collection's methods.
Previously, I had the View/Collection refer to each other directly by their variable names.
Since I have separated the code into modules, the View/Collection cannot access each other directly anymore.
Here is some of the code: (written in Coffeescript)
app.coffee - global_dispatcher is applied to Backbone
define [
'jquery'
'underscore'
'backbone'
'cs!router'
], ($, _, Backbone, Router) ->
# global_dispatcher added to all Backbone Collection, Model, View and Router classes
dispatcher = _.extend {}, Backbone.Events, cid: 'dispatcher'
_.each [ Backbone.Collection::, Backbone.Model::, Backbone.View::, Backbone.Router:: ], (proto) ->
_.extend proto, global_dispatcher: dispatcher
new Router()
router.coffee - This is where I'm having trouble. The function for 'getResults' is triggered, but the collection 'results' is not accessible from here.
define [
'backbone'
'cs!views/resuls/list'
'cs!views/results/search'
'cs!collections/results'
], (Backbone, ListView, SearchView, Results) ->
Backbone.Router.extend
routes:
# URL routes
'': 'index'
index: ->
results = new Results
new ListView { model: results }
new SearchView
#global_dispatcher.bind 'getResults', (data) ->
console.log results
search.coffee - View which triggers the event, it will successfully trigger the event and pass the correct arguments.
define [
'jquery'
'backbone'
], ($, Backbone) ->
Backbone.View.extend
events:
'submit #search-form': 'submit'
submit: (evt) ->
evt.preventDefault()
phrase = #.$('input').val()
#.$('input').val('')
args = name: phrase
#global_dispatcher.trigger 'getResults', args
If I'm understanding your problem correctly it's not hard to solve. Here's some dummy code to illustrate:
var Results = Backbone.Collection.extend();
var Search = Backbone.View.extend({
someEventHandler: function() {
// The next line accesses the collection from the view
this.collection.fetch();
}
});
var results = new Results();
var search = new Search({collection: results});
If you want your view to do something after the results come back, just bind an event handler on the collection:
var Search = Backbone.View.extend({
fetchResposne: function() { /* do something*/},
someEventHandler: function() {
// The next line accesses the collection from the view
this.collection.on('sync', this.fetchResponse);
this.collection.fetch();
}
});
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.
I am beginning to learn Backbone.js and I started with this boilerplate and made an example by loading JSON data from a static file on disk and display it in an html table.
Then I tried to bind an event on a button which is supposed to delete an element from the collection, then from the DOM. The thing works fine, the click triggers the destroy method, the remove event is triggered on the collection, but nothing comes out of the success or error callbacks of destroy
Anybody have a clue ?
The model :
define([
'underscore',
'backbone'
], function(_, Backbone) {
var memberModel = Backbone.Model.extend({
url: "/members.json",
defaults: {
email: "",
firstname: "",
lastname: "",
gender: "",
language: "",
consent: false,
guid: "",
creationDate: ""
},
initialize: function(){
}
});
return memberModel;
});
The view :
define([
'jquery',
'underscore',
'backbone',
'mustache',
'collections/members',
'text!templates/members/page.html'
], function($, _, Backbone, Mustache, membersCollection, membersPageTemplate){
var membersPage = Backbone.View.extend({
el: '.page',
initialize: function(){
this.members = new membersCollection();
this.members.on('remove', function(){
// works fine
$('.members-body tr').first().remove();
console.log('removed from collection');
});
},
render: function () {
var that = this;
this.members.fetch({success: function(){
var wrappedMembers = {"members" : that.members.toJSON()};
that.$el.html(Mustache.render(membersPageTemplate, wrappedMembers));
$('#delete-member').click(function(){
that.members.at(0).destroy({
// prints nothing!!!
success: function(){ console.log('sucess'); },
error: function(){ console.log('error'); }
});
});
}});
}
});
return membersPage;
});
I agree that this is odd. I'm not entirely sure what's happening yet, but here is what I suspect...
Your Destroy() calls aren't returning valid JSON.
Watching firebug,fiddler, or whatever, what do your destroy() responses look like?
I'm also curious what that is when your delete-click function is triggered.
Does destroy return false or a jqXHR object?
There is a bit a disconnect in backbone (at least i was for me at first). When calling destroy() or fetch/save for that matter, the call is async. Immediately after the call the delete event fires and your collection can respond. But, digging a bit deeper in the documentation, another event is fired up confirmation of the delete:
and a "sync" event, after the server has successfully acknowledged the model's deletion
So your collection is acting on the presumption that the delete has succeeded. If you want your collection to respond to a confirmed delete, look for the sync event.
This leaves one nagging point -- why isn't your error handler firing? Well, the callbacks are designed to respond to the confirmation of a delete. But I bet the JS call stack doesn't know how to interpret the response it's getting back, as a success or error. You likely stumbled across the realities of AJAX. I've found in backbone, jquery, and bunch of other frameworks that you can confuse the ajax error handling. Google "jquery ajax error not called" and you'll find a host of different scenarios where the error event isn't triggered.
UPDATE
After the comments back and forth...two things are happening. First, your model is perceived as 'new' which means calls to Destroy() don't make server requests. As a result your success/error don't fire. See this commit.
With that said, I don't think you consider your model new (not persisted to the server). You need to do one of two things. Either include a property named id OR in your model map your models ID (guid i assume) to the ID of the model. Mapping is easy by appling the following line to your model: idAttribute: "guid". You can see more on the idAttribute here.