Passing a collection from a router to a view - backbone.js

It's throwing this error: Uncaught TypeError: Cannot call method 'fetch' of undefined in the view. #collection is undefined in the view, despite seemingly passing it in through the router.
If you're curious, the ajaxSubmit is being used to handle a file upload.
Any ideas?
routers/tracks.js.coffee
...
initialize: ->
#collection = new Jambox.Collections.Tracks()
#collection.fetch() # works!
new: (name) ->
view = new Jambox.Views.TracksNew(name, collection: #collection)
views/tracks/new.js.coffee
events:
'click #submit': 'uploadTrack'
uploadTrack: (e) ->
e.preventDefault()
$(#el).find('#new-track').ajaxSubmit(
success: => #collection.fetch() # Uncaught TypeError
)
...

The context (AKA # or this) for the success callback probably isn't your view object, it is probably the same context used for $.ajax and that is:
By default, the context is an object that represents the ajax settings used in the call ($.ajaxSettings merged with the settings passed to $.ajax)
You should bind your success callback to the view with a fat-arrow (=>):
uploadTrack: (e) ->
e.preventDefault()
$(#el).find('#new-track').ajaxSubmit(
success: => #collection.fetch()
)
...

I found the problem. This is why it wasn't passing the collection through to the view.
Originally:
new: (name) ->
view = new Jambox.Views.TracksNew(name, collection: #collection)
The fix:
new: (name) ->
view = new Jambox.Views.TracksNew(name: name, collection: #collection)

Related

AngularJS $resource update/ PUT operation?

I'm confused as to how exactly to update a resource using $save. I've read the angular resource documentation and looked at other posts on stack overflow but I cannot seem to perform an update operation on an existing object.
For example, I have an event object and I want to update its name and location properties. I have the start of a function that correctly takes in the eventId of a singular event.
Here is the function so far:
eventService.updateEvent = function (eventId, eventName, eventLocation) {
// Defines the resource (WORKS)
var Event = $resource('/api/events/:id/', {id:'#_id'});
// Gets the event we're talking about (WORKS)
var event = Event.get({'id': eventId});
// TODO update event
};
How do i successfully update this resource?
Figured it out!
When I defined the resource, I defined the PUT operation as a custom method called 'update'.
I called get on that resource and looked up a particular object by ID.
Using a promise, I was able to update the resource using the 'update method' if the object was found, else throw an error.
eventService.updateEvent = function (eventId,eventName,eventLocation) {
// Define the event resource, adding an update method
var Event = $resource('/api/events/:id/', {id:'#_id'},
{
update:
{
method: 'PUT'
}
});
// Use get method to get the specific object by ID
// If object found, update. Else throw error
Event.get({'id': eventId}).$promise.then(function(res) {
// Success (object was found)
// Set event equal to the response
var e = res;
// Pass in the information that needs to be updated
e.name = eventName;
e.location = eventLocation;
// Update the resource using the custom method we created
Event.update(e)
}, function(errResponse) {
// Failure, throw error (object not found)
alert('event not found');
});
};

Trouble running underscore templates

I'm unable to run render HTML from an Underscore template. Here's the code and the error:
//Code
class QuestionView extends Backbone.View {
template:(data:any) => string = null;
$el = $root;
constructor(options?:any, question?:Question) {
super(options);
this.model = question;
var q = this;
require(["text!add-new-question.html"],
function (html) {
q.template = _.template(html);
}
);
}
render() {
var data = this.model.toJSON();
var html = this.template(data);
this.$el.html(html);
return this;
}
}
class SurveyView extends Backbone.View{
tagName = "div";
template:(data:any) => string = null;
$el = $root;
constructor(options?:any, survey?:Survey){
super(options);
this.template = _.template("<div ><%= enlistQuestions %></div>");
}
enlistQuestions():string{
var questions = "";
_.each(this.collection.models, function(question:Question){
var view = new QuestionView(null,question);
questions += view.template(view.model.toJSON()); // This is line for which the error is shown.
});
return questions;
}
}
//Error
Uncaught TypeError: Property 'template' of object #<QuestionView> is not a function Main.ts:63
(anonymous function) Main.ts:63
j.each.j.forEach underscore.js:79
SurveyView.enlistQuestions Main.ts:61
SurveyView.render Main.ts:69
SurveyView Main.ts:56
(anonymous function) Main.ts:80
Looks like you are using the "text" AMD loader plugin. If not, disregard this answer.
This plugin loads the resource asynchronously and the function supplied to the require statement is called back after the file is loaded.
It appears that the Main is attempting to use the template property on the QuestionView instance before the callback has chances to create it, hence the error.
The text module creates the XHR object and invokes open set the async flag to true. You need to set this flag to false. The only way this is possible is to use the onXhr callback and invoke OPEN again setting the async flag to false.
requirejs.config({
config: {
text: {
onXhr: function (xhr, url) {
// This callback is invoked AFTER open but before send.
// Call open again with the async flag turned off.
// Not this will also clear any custom headers
xhr.open('GET', url, /*async*/ false);
// xhr.setRequestHeader('header', 'value');
}
}
}
});
As noted above, any custom HTTP header added by the text module will be cleared when you re-invoke open, but looking at their code there doesn't appear to be any way of supplying extra headers into their 'get' function.

BackboneJS MarionetteJS - waiting until collection has finished fetching

I have a BackboneJS & MarionetteJS app
class MyApp extends Marionette.Application
app = new MyApp
app.addRegions
tag_container :"#tag_container"
item_container :"#item_container"
app.addInitializer( =>
app.items = new ItemCollection()
app.item_container.show(new ItemListView({collection:app.items}))
)
In my ItemCollection,
class ItemCollection extends Backbone.Collection
model :ItemModel
url :"/get_items"
initialize: =>
#search()
search: =>
#reset()
#fetch()
Above code displays the ItemListView immediately and adds the items as they are being fetched.
How can I wait until the collection has finished fetching THEN display the ItemListView in the "item_container"?
Either use a listener to know when the collection has finished his job. You can listen to reset if you're resetting it (not the default behavior as of Backbone 0.9.10) or sync (always done).
Or you can use the success callback.
Source
A third solution would be to do a synchronous fetch:
#fetch async: false
fetch returns a jquery promise. so you can just do
collection.fetch().done(function() {
// create & show the view
});

Backbone Router has no method navigate

Just wondering why my router has no method navigate?
I have new App.Router; called properly. And in my view I try to call my index view like so:
addTrip: function(e){
e.preventDefault() ;
//Adding to collection
var newTrip = this.collection.create({
title: this.$('#new_trip #title').val(),
where: this.$('#new_trip #where').val(),
desc: this.$('#new_trip #desc').val(),
rating: this.$('#new_trip .active').text()
}, { wait: true }) ;
if(newTrip.validationError){
this.errorReport(newTrip.validationError) ;
}
this.clearForm() ;
//Navigate to home route
App.Router.navigate('', true) ;
}
I get the following error in Chrome Dev Tools:
Uncaught TypeError: Object function (){ return parent.apply(this, arguments); } has no method 'navigate'
I even tried to call navigate from the console and it doesnt seem to work either.
What am I doing wrong?
You should call navigate on your router object. It seems you are calling it on the class itself.
App.myRouter = new Backbone.Router() //or if you have your own base router class App.myRouter = new App.Router()
myRouter.navigate('', true);

BackboneJS Flash Messages

Is there an extension to backbone for Flash Messages? It seems to be a common feature in Web Frameworks (server side at least). There appears to be none, and I attempted to make my own:
class FlashMessenger extends Backbone.Model
constructor: ->
#messages = []
# add a message to the messages array
add: (type, message) ->
#messages.push
type: type
message: message
# returns all existing messages and clearing all messages
getMessages: ->
ret = #messages.slice(0)
#messages = []
return ret
Now, I was wondering how can I inject them into my views automatically. I will like my messages to show when I use Backbone.Router.navigate() eg:
app.flashMessages.add("success", "Successfully logged in")
appRouter.navigate("dashboard")
# flash messages should show when I render the view
My 5 cents -- it seems to be a bit of an overkill to use Backbone for flash messages. If you have only 1 instance of flash message on the page, you're better off not using a separate model for it.
Instead I would use a view for Flash message and a global dispatcher:
Dispatcher = _.extend({}, Backbone.Events);
Create view:
var FlashMessage = Backbone.View.extend({
initialize: function() {
Dispatcher.bind('show_flash_message', this.render);
},
render: function(msg) {
// do something with the message
}
});
And from the part of the app where you have to show the flash message, do
Dispatcher.trigger('show_flash_message', 'Some message');

Resources