Backbone.js read model attributes in initialize - backbone.js

In one of my view I need to recopy an attribute value into another attribute.
Here is the code in my code in coffeescript
class MyFactoryView extends Backbone.View
initialize: ->
#model.fetch reset: true
#model.set('NewStatus', #model.get('CurrentStatus'))
This code throws an undefined exception when I call get.
But if I do a console.log #model, I can see the CurrentStatus in attributes.

As mu said, fetch is async and you only have access to the attributes once it's finished.
class MyFactoryView extends Backbone.View
initialize: ->
#model.fetch
reset: true
context: #
success ->
#model.set('NewStatus', #model.get('CurrentStatus'))
For that specific line, it could be simpler to use the parse function of the model to initialise the NewStatus attribute on sync.
For the console, take a look at this answer which explains that the console contains live references and doesn't copy anything.

Related

Deleting a backbone collection both from the view and the server

Struggling to make the following code work in a view that manages a backbone collection:
class VGStream.Views.Scenarios.ScheduleRisks extends Backbone.View
template: JST['backbone/templates/scenarios/schedule_risks']
initialize: (options) ->
super
Backbone.pubsub.on 'allSchedulesRemoved', #removeAllScheduleRisks, #
removeAllScheduleRisks: =>
for risk in #risks.models
risk.destroy
#render()
render: ->
super
#risks.fetch()
#$el.html #template #
#
I am just showing the relevant code snippet from the Coffeescript class view. This does not delete the 'risk' models either from the database or the list view itself. How should I do it? The event allSchedulesRemoved fires correctly when all schedules are removed. I want the associated risks to be removed as well from both the view as well as the database.
You don't always have to call super
There are built-in #risks.each() method to loop through collections.
Put fetch() in render() is not recommended.
To clear the collection, you could do several #risks.remove(risk) or, after destroyed everything, reset the collection with an empty object.
risk.destroy actually send a delete request to your backend, so whether it's deleted in server or not depends on your backend code.

What do I get with the default Backbone.Model initialize function?

For context I use coffeescript. If I create a base model that extends Backbone.Model and I create another class (i.e. App.Models.Project extends App.Models.Base).. everything works as expected.. what would be the difference to an instance of Project if in this base class I wrote:
initialize: ->
super
console.log 'hi'
and just plain
initialize: ->
console.log 'hi'
Without spending too much time, it seems in my console an instantiated object acts as expected in both cases.. I hear you should 'always call super' here but I don't know what I'm getting..
Backbone.Model.initialize does nothing.
From the annotated source code, you can see the empty function defined in Backbone.Model
initialize: function(){}
It's upto your model to override. Usually, model variables are set here. Whenever you create a model object, initialize is called internally.
The same principle holds good when creating Views and Collections too.

Can't extend backbone.events in coffeescript

I'm getting the error:
Uncaught TypeError: Cannot read property 'constructor' of undefined
When declaring the following class:
class ViewHelpers extends Backbone.Events
I can use the same syntax to extend Backbone.Router, Views, Model etc. Here is the compiled javascript which I wrote in a quick log to make sure Backbone.Events was there
__t('views').ViewHelpers = (function(_super) {
#how i know it is definied here
console.log(_super.trigger)
__extends(ViewHelpers, _super);
function ViewHelpers() {
return ViewHelpers.__super__.constructor.apply(this, arguments);
}
return ViewHelpers;
})(Backbone.Events);
So the line causing the error is
ViewHelpers.__super__.constructor.apply(this, arguments);
What is different about __extends() method that it would work for Backbone.View and not Backbone.Events?
That's because Backbone.Events is not a "class", so it cannot be extended, it's a "module" that can be mixed-in into other objects (see docs here). In JavaScript terms that means that it's not a Function, that can be called as a constructor (i.e. new Backbone.Events will throw an error), it's just a plain JS object whose properties (methods) can be assigned to other objects to make them event dispatchers.
In CoffeeScript, you can mix-in the Backbone.Events into your objects when they are created:
class ViewHelpers
constructor: ->
_.extend #, Backbone.Events
Or you can just extend the class' prototype and avoid having those methods as (own) properties of all ViewHelpers instances:
class ViewHelpers
_.extend #prototype, Backbone.Events
These two approaches should work and let you instantiate and use ViewHelpers as event dispatchers:
vh = new ViewHelpers
vh.on 'foo', -> alert 'bar'
vh.trigger 'foo'​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​
There's another way (from what #epidemian answered), which doesn't involve copying Backbone.Events into a new object to use as your prototype - instead, use Object.create to create a new object to use as your prototype, using Backbone.Events as its prototype.
class ViewHelpers
#prototype = Object.create(Backbone.Events)
Now ViewHelpers' prototype is a new, empty object whose prototype is Backbone.Events. You can define methods on ViewHelpers' prototype without affecting Backbone.Events, but all the Backbone.Events methods are still available to ViewHelpers, without having to copy them into a new object. This not only saves (a miniscule amount of) memory, but if you ended up adding on to Backbone.Events later, all ViewHelperss would see the change.
For this, you'll need either a browser that has ES5's Object.create function, or an Object.create polyfill.
To build on the excellent answer by #epidemian I would add this, it's a bit of a hack but it allows you to write your class with the extends statement as you specified in the question (which lets you call super on all the Backbone.Events methods):
class Events
_.extend Events.prototype, Backbone.Events
class CustomEvents extends Events
trigger: (event, etc...) ->
# You can add overrides before
super "custom:#{event}", etc...
# or after the super class methods
It would be neat to work the _.extend call into the Events.constructor function but I couldn't make it work...

Backbone.js extend and this

I'm using experimenting with .extend() to set up my views and initialise them with. I've found it's convenient to assign config variables to view objects nested deep within a hierarchy.
My problem is that my Views lose their this context. This becomes the ctor object which I asume is the constructor. How can I fix this?
My coffeescript is below. The first class would be nested deep within a tree, the second is at the top level where the application boots up:
# This is a child somewhere deep within a tree of views.
class View extends Backbone.View
initialize: ->
console.log # # returns object ctor
MyView = View.extend({
initialize: ->
# do config stuff then init prototype
App.Views.MyView.prototype.initialize()
})
view = new MyView
Two things:
First, and not as importantly, you can use
class MyView extends View
instead of View.extend. CoffeeScript classes and Backbone classes are interoperable.
Second—and this is the important part—instead of
App.Views.MyView.prototype.initialize()
you should simply use the CoffeeScript keyword
super
That effectively does the same thing, but also ensures that the function is called in the correct context. Bonus: It also passes in all of your function arguments for you.
If you're curious, super here compiles into
initialize.__super__.constructor.apply(this, arguments)
(where __super__ is a reference to the superclass that's set by both CoffeeScript's extends). Read about apply at MDN.
My coffe script isnt so hot but can you call the function sending the view you want to be the value for this in as the first parameter
I suppose in standardish js
var view = null;
MyView = View.extend({
initialize: function() {
// do config stuff then init prototype
App.Views.MyView.prototype.initialize.call(view)
}
})
view = new MyView;
Im not sure what you have access to at that point or the generated js either.
The point being if you have access to what should be the value for this when you call the function you should be able to pass it in.
I would check myself but i don't comprehend coffeescript :)

Backbone.js - where does code to create a new post go?

I'm trying to make a quick sample Backbone.js app that has a Post model, a PostList collection, and a PostView + PostListView. Something simple where you can post in a form and it will add your post to the list of posts.
When someone clicks submit on the post form, it triggers an event in "PostListView", the view for the PostList collection. Where do I create a new post model and add it to the collection? Do I write this code in the View itself? Or does the view call a collection method that does this? Can you even write custom collection methods? If so, how do I call them from a view?
Coming from a Rails background, I naturally edge towards putting code in the collections/models rather than views (rails controllers), but I can't figure out how to call custom collection events from views.
Code is as below. Thanks so much for any help!
PostListView.coffee:
class forum.PostListView extends Backbone.View
tagName: 'section'
className: 'post-list'
events:
'click .post-form button': 'submit'
initialize: ->
#causes the view to render whenever the collection's data is loaded
#collection.bind 'reset', #render
#collection.bind 'add', #render
render: =>
$(#el).html JST['postList']()
$postList = this.$('.post-list')
#iterates through posts, renders, appends to <ul>
#collection.each (post) =>
view = new forum.PostView
model: post
collection: #collection
$postList.append view.render().el
return this
submit: ->
console.log "submitted!"
#collection.trigger 'newPost', this.$('.post-form textarea').val()
PostList.coffee:
class forum.PostList extends Backbone.Collection
model: forum.Post
url: '/posts'
initialize: ->
this.bind 'newPost', newPost
newPost: (postText) ->
console.log "Collection method called!!"
# post = new forum.Post
# content: postText
# #add post
You call a method in PostList, from the PostListView, to add a new model in the collection. Something like this in regular js:
this.collection.add( new forum.Post( this.$( ".post-form textarea").val() ) );
I don't do CoffeeScript so I guess the syntax for above in cs would be like this:
#collection.add new forum.Post
this.$( ".post-form textarea").val()
If it's more complicated to add a new Post to PostList, you could add a method for it in PostList
this.collection.complexAdd( params... );
You add custom methods to PostList in the place where you create the class.. you have done so with the newPost method already.
This is much simpler than your collection listening to view events because views are initialized last.
Edit: I guess it could also come down to opinion, some people prefer models/collections knowing about which backbone views are using them, but in a situation where there are more than one view for one model (for example, TabContent view and TabSelector view for a single TabModel), it makes stuff more complicated.
The following tips may help you...
It might separate things better if you have a separate view for NewPost, whose single responsibility is to manage the new post form. Your PostListView can create that and append it to itself.
Normally, you do not want to add a new model to a collection until after it is saved. So what you may do is give your NewPostForm a reference to the collection and have it add it once it is saved.
You may also want the PostList to dump and recreate the NewPost view after that, and get it ready to add a subsequent post.
Backbone "views" are sometimes more like controller actions in Rails. So creating models, moving them around, saving them, etc. are all perfectly ok to do in view code.

Resources