model.toJSON is not a function using Backbone - backbone.js

This :
#model
Returns :
Object { type="conjugation", verb="ser", yo="soy", more...}
But when I try :
#model.toJSON()
I get :
TypeError: this.model.toJSON is not a function
I am trying to eventually complete this line :
$(#el).html(#template(#model.toJSON() ))
So that I can render this object in a Show with my template.
Any recommendations?
Update
Persuant the comments. I have this as a model, but I can see now how they're are not related.
class AiProject.Models.Verb extends Backbone.Model
paramRoot: 'verb'
I'm going to try and instantiate this type of verb.
class AiProject.Routers.QuestionsRouter extends Backbone.Router
initialize: (options) ->
#verb = new AiProject.Models.Verb
#verb = options.words
And then back to my View :
class AiProject.Views.Questions.ConjugationView extends Backbone.View
template: JST["backbone/templates/questions/conjugation"]
render: ->
$(#el).html(#template(#model.toJSON() ))
Still get the same error though..

It looks like you're setting your model correctly at first, then overwriting it with the value options.words.
Instead of this:
class AiProject.Routers.QuestionsRouter extends Backbone.Router
initialize: (options) ->
#verb = new AiProject.Models.Verb
#verb = options.words
Try this:
class AiProject.Routers.QuestionsRouter extends Backbone.Router
initialize: (options) ->
#verb = new AiProject.Models.Verb(options.words)
That creates your model and passes in options.words to be set as the model's attributes.

Related

Why .filter() in Marionette.CollectionView doesn't work?

Why .filter() in Marionette.CollectionView doesn't work?
It just isn't fired.
P.S. Collection has one element.
1 file:
documents = new Collections.Documents
documents.fetch().done =>
#getRegion('certificates').show(new Views.CertificatesCollectionView(documents))
#getRegion('diplomas').show(new Views.DiplomasCollectionView(documents))
2 file:
class Views.DiplomasCollectionView extends Marionette.CollectionView
initialize: (#collection) ->
console.log 'intzd'
childView: Views.DocumentItemView
filter: (document_model) ->
console.log 'fitr'
document_model.is_diploma()
try with:
#getRegion('diplomas').show(new Views.DiplomasCollectionView(collection: documents))
and
class Views.DiplomasCollectionView extends Marionette.CollectionView
childView: Views.DocumentItemView
filter: (document_model) ->
console.log 'fitr'
document_model.is_diploma()

Dynamically set backbone collection url returns models without attributes

I have a backbone mobile application that is tied to a Rails web service. All models of other objects are loaded and displayed correctly, but this particular one does not. The issue I am having is that Item gets created, but does not load the attributes. I have confirmed correct json output of the rails api. The difference between this collection and all other collections of this application is that this collection has a parentID that must be used in the url to load the correct items (category_id).
Now... the funny thing is that if I remove the {category_id: options.attributes[0].category_id} argument to the constructor call of ItemCollectionView and hard code a category_id directly into the url, it works! (however I need to assign this dynamically based on the category of the parent view.
Here are my classes:
export class ItemCollectionView extends Backbone.View {
public template: string;
public collection: itemsImport.Items;
constructor(options?: Backbone.ViewOptions){
this.collection = new itemsImport.Items({category_id: options.attributes[0].category_id});
super(options);
}
...
public addOne(item: itemImport.Item): void{
var view: itemItemImport.ItemItemView = new itemItemImport.ItemItemView({el: this.el, model: item});
//Breakpoint right here shows that item.attributes.id = undefined
view.render();
}
}
export class Items extends Backbone.Collection {
public url: string;
constructor(attributes?: any, options?: any){
this.url = 'http://localhost:3000/categories/' + attributes.category_id + '/items';
this.model = itemImport.Item;
super(attributes, options);
}
}
In my debugging I can confirm that:
options.attributes[0].category_id == 1;
and
this.url == 'http://localhost:3000/categories/1/items';
and the response from that url is:
[{"id":1,"category_id":1,"title":"Item 1","description":null,"active":true,"comment":null,"extra":null,"deleted":"0","url":"http://localhost:3000/categories/1/items/1.json"},{"id":2,"category_id":1,"title":"Item 2","description":null,"active":true,"comment":null,"extra":null,"deleted":"0","url":"http://localhost:3000/categories/1/items/2.json"}]
which you can see is a correct json response.
So my question is: What am I doing wrong? || What is the correct way to dynamically pass variables into collections to set the correct url at runtime?
Thank you for your help
Define url as function like in second case here
var Notes = Backbone.Collection.extend({
url: function() {
return this.document.url() + '/notes';
}
});
And check network tab if you really load correct url.
The other answer selected will work if using javascript, but if using TypeScript the compiler will complain that url must be a property, not a method. The solution I found (which will work in javascript as well) is to set the url argument on the fetch() method of the collection like below
export class ItemCollectionView extends Backbone.View {
public template: string;
public collection: itemsImport.Items;
constructor(options?: Backbone.ViewOptions){
this.collection = new itemsImport.Items({category_id: options.attributes[0].category_id});
super(options);
}
public initialize(options?:any): void {
this.listenTo(this.collection, 'add', this.addOne);
this.listenTo(this.collection, 'reset', this.addAll);
this.collection.fetch({url: this.collection.seturl(options.attributes[0].category_id)});
}
...
}
I hope this helps future users looking for this functionality in Backbone
It is possible to define a dynamic url property to your Backbone model in your Typescript code. This would be better than using the url parameter in all your calls to fetch().
You could either do it using an ECMAScript 5 getter:
export class Items extends Backbone.Collection {
get url(): string {
return '/categories/' + this.get('category_id') + '/items';
}
constructor(attributes?: any, options?: any){
this.model = itemImport.Item;
super(attributes, options);
}
}
or by setting it directly on the prototype:
export class Items extends Backbone.Collection {
constructor(attributes?: any, options?: any){
this.model = itemImport.Item;
super(attributes, options);
}
}
Items.prototype.url = function() {
return '/categories/' + this.get('category_id') + '/items';
};
The second solution will work on all browser.

How to treat nested collection events in Marionette?

I have an API resource that gives me a list of users that each have several items. The hierarchy is like so:
- users
- user
- items
- item
- item
- item
- user
- items
- item
- item
- item
I would like to display the list of users on a single page, with each user entry displaying each of its items on the page as well.
When any one of these items is clicked, it should set an chosen attribute that is accessible through the overall users collection.
I'm having difficulty getting the item click information to bubble back up. My current implementation is creating a separate items collection in order to render the view, but then I lose the connection to its original user model, so I can't notify it when the item is selected.
My views are structured like so:
class List.Item extends Marionette.ItemView
template: "path/to/template"
events:
"click" : "choose"
choose: (e) ->
# what to do?
class List.User extends Marionette.CompositeView
collection: #collection
template: "path/to/template"
itemView: List.Item
itemViewContainer: "span"
initialize: ->
#collection = new App.Entities.Items(#model.get("items"), parent: #)
events:
"click a" : "toggleChoose"
#include "Chooseable"
class List.Users extends Marionette.CollectionView
itemView: List.User
Is there a better way to structure these collections or views, or is there a way to pass the information from the List.Item view to the parent List.User view and then into the users collection?
EDIT
I have tried backbone-relational, but it didn't seem to quite do what I need. Please correct me if I'm wrong here.
Your List.Item should contain it's current model with all properties at the time when choose is triggered. In this way, you can trigger other events with the List.Item's model values:
choose(e) : ->
trigger("mylistitem:choose", model)
Then listen for the event elsewhere :
itemView.on("itemview:mylistitem:choose", ( childView, model ) -> {
alert(model.get('..whatever..')
}
It is actually possible to instantiate the items collection to reference the parent user and vice-versa directly in Backbone:
class Entities.User extends Backbone.Model
...
initialize: ->
#items = new Entities.Items #get("items"),
user: #
class Entities.Items extends Backbone.Collection
...
initialize: (models, options) ->
#user = options?.user
So now the List.User CompositeView can pass this information to the List.Item ItemView:
class List.User extends Marionette.CompositeView
collection: #collection
...
initialize: ->
#collection = #model.items
With this in place, it is possible to access the user directly from the ItemView:
class List.Item extends Marionette.ItemView
...
events:
"click" : "choose"
choose: (e) ->
e.preventDefault()
user = #model.collection.user
console.log "user: ", user
And from there it's possible to take any necessary actions on the user and its collection.

How to get trigger to listen properly

I am having a hard time getting my trigger to respond properly. I have plenty that are working but one of them isn't and I can't understand why.
Here is my AppController class
class ProjectOrder.View.AppController extends Backbone.View
initialize: ->
#promptOfficeSearch()
promptOfficeSearch: ->
officeSearch = new ProjectOrder.View.OfficeSearch
officeSearch.on 'createOffice', #promptOfficeCreate
officeSearch.on 'createTicket', #promptTicketCreate
officeSearch.on 'accountAndTicketExist', #killProcessAccountExists
promptOfficeCreate: (serial) ->
#officeModel = new ProjectOrder.Model.OfficeModel()
#officeModel.set('serial_number', serial)
officeCreate = new ProjectOrder.View.OfficeCreator({model: #officeModel})
officeCreate.on 'createTicketOffAccount', #promptTicketCreate
promptTicketCreate: (model) ->
console.log 'promptTicketCreate'
model = model || #officeModel
ticketModel = new ProjectOrder.Model.TicketModel()
new ProjectOrder.View.TicketCreator({ticketModel: ticketModel, officeModel: model})
killProcessAccountExists: (ticket_id) ->
msg = document.createElement 'div'
msg.className = 'account-exists-msg'
msg.innerHTML = "Account already exists. Redirecting to ticket #{ticket_id}..."
$('#create-order-div').append(msg)
setTimeout((->
window.location = "/pto/#{ticket_id}"
), 2000)
All of the triggers from the officeSearch object in the promptOfficeSearch function work properly. They are all triggered as follows, respectively:
#trigger 'createOffice', serial
#trigger 'createTicket', data.model[0]
#trigger 'accountAndTicketExist', data.model
But with the officeCreate object in the promptOfficeCreate, it does not respond to the createTicketOffAccount event which is registered in the submitOffice ajax success callback in my OfficeCreator class:
class ProjectOrder.View.OfficeCreator extends Backbone.View
template: _.template($("#OfficeCreator").html())
id: 'office-creator'
events:
'click .submit' : 'submitOffice'
initialize: ->
#render()
render: ->
#$el.html(#template(#model.toJSON()))
$('#create-order-div').append(#$el)
submitOffice: ->
#setModelData()
#model.save(null,{
success: (model) =>
#trigger 'createTicketOffAccount', model
##$el.remove()
error: ->
alert 'error'
})
setModelData: ->
#model.set({
office_name: $('#office').val()
doctor_name: $('#doctor').val()
emr: $('#has-emr').is(':checked')
forms_builder: $('#has-forms').is(':checked')
iehr: $('#has-iehr').is(':checked')
clipboard: $('#has-clip').is(':checked')
specialty_id: $('#specialty').val()
})
any ideas why my trigger is not working?
I think you need fat arrows on all the methods in your AppController class.
When this event fires:
officeSearch.on 'createOffice', #promptOfficeCreate
the promptOfficeCreate function gets invoked as a normal function as opposed to a method bound to your controller instance as this, so when this happens:
officeCreate.on 'createTicketOffAccount', #promptTicketCreate
#promptTicketCreate is undefined and the event binding doesn't wire up properly.

How do you do anonymous classes in Coffeescript?

I have a working coffeescript/backbone idiom that looks like this:
SidebarWidgets = ((() ->
SidebarWidgets = { }
class SidebarWidgetPrototype extends Backbone.View
initialize: (options) ->
#template = $(options.templateId).html()
render: () ->
$(#el).html(_.template(#template, #model.toJSON()))
#el
class SidebarWidgets.user extends SidebarWidgetPrototype
class SidebarWidgets.shoppingcart extends SidebarWidgetPrototype
class SidebarWidgets.messages extends SidebarWidgetPrototype
SidebarWidgets
)())
class Sidebar extends Backbone.View
views: ['user', 'shoppingcart', 'messages']
initialize: (options) ->
#subviews = { }
_.each(#views,(v) =>
subviews[v] = news SidebarWidgets[v](
model: cxDatasets[v]
id: 'sidebar-' + v
templateId: '#sidebar-' + v + 'template'
)
)
render: () ->
$(#el).html()
_.each(#views, (v) =>
$(#el).append(#subview(v).render())
)
The intent of this idiom is to provide a list of backbone views that the sidebar view will then incorporate, while providing the opportunity (but not the necessity) to override or enhance one or more methods of a widget.
The thing that irks me is that, for those views that do not need modification, they still need to be named explicitly by the class syntax of Coffeescript.
Is there a way to create an anonymous class with the Coffeescript syntax? Can you say something like (the following is pseudocode):
thisclass = extend BackboneView
initialize: (options) ->
If so, how?
thisclass = class extends BackboneView
initialize: (options) ->

Resources