The App Layout
I am building an App, where one can create surveys. Every survey has multiple questions. I am embedding the questions into the survey model (with embeds_many
in Mongoid), so a survey may look like this:
{
"id": "4f300a68115eed1ddf000004",
"title": "Example Survey",
"questions":
[
{
"id": "4f300a68115eed1ddf00000a",
"title": "Please describe your experience with backbone.js",
"type": "textarea"
},
{
"title": "Do you like it?",
"id": "4f300a68115eed1ddf00000b",
"type": "radiobutton",
"options": ["Yes", "Yes, a lot!"]
}
]
}
Now, there is also a survey editor which consists of a SurveyView
, which displays the survey and lists the questions. If I click on a single question, a QuestionView
will pop up, where I can edit the question. And when I am satisfied with my survey and I click save, the SurveyModel
will be sent to the server.
The problem
What is the best way to handle the embedded association?
If I pass survey.get("questions")[any_index]
to the QuestionView
, and the question gets changed, I have to manually search for the question.id
in my model and update my model. This feels wrong.
If I create a QuestionsCollection
in my SurveyModel
(is this even possible?). Then I can do things like fetching a Question
out of this collection by id, pass it to the view and when I change the model, everything will get updated automatically, but I have to specify an url
in the collection, and backbone will send single questions to the server, if things get updated.
Any suggestion on how to do this the backbone way?
The App Layout
I am building an App, where one can create surveys. Every survey has multiple questions. I am embedding the questions into the survey model (with embeds_many
in Mongoid), so a survey may look like this:
{
"id": "4f300a68115eed1ddf000004",
"title": "Example Survey",
"questions":
[
{
"id": "4f300a68115eed1ddf00000a",
"title": "Please describe your experience with backbone.js",
"type": "textarea"
},
{
"title": "Do you like it?",
"id": "4f300a68115eed1ddf00000b",
"type": "radiobutton",
"options": ["Yes", "Yes, a lot!"]
}
]
}
Now, there is also a survey editor which consists of a SurveyView
, which displays the survey and lists the questions. If I click on a single question, a QuestionView
will pop up, where I can edit the question. And when I am satisfied with my survey and I click save, the SurveyModel
will be sent to the server.
The problem
What is the best way to handle the embedded association?
If I pass survey.get("questions")[any_index]
to the QuestionView
, and the question gets changed, I have to manually search for the question.id
in my model and update my model. This feels wrong.
If I create a QuestionsCollection
in my SurveyModel
(is this even possible?). Then I can do things like fetching a Question
out of this collection by id, pass it to the view and when I change the model, everything will get updated automatically, but I have to specify an url
in the collection, and backbone will send single questions to the server, if things get updated.
Any suggestion on how to do this the backbone way?
Share Improve this question edited Feb 12, 2012 at 12:13 iblue asked Feb 11, 2012 at 21:52 iblueiblue 30.4k20 gold badges92 silver badges129 bronze badges 1- Could you please paste the final code in javascript? not the coffeescipt. thanks. – Joe.wang Commented Mar 1, 2013 at 2:04
4 Answers
Reset to default 15The way to do embedded one-to-many associations in backbone.js
I have implemented the answer from @Sander and just wanted to post some code:
class Survey extends Backbone.Model
# Handles the Survey.questions association to parse stuff from the server
parse: (resp) ->
if @attributes?.questions?
@attributes.questions.reset(resp.questions)
else
resp.questions = new QuestionsCollection(resp.questions)
resp
# Recollects Survey.questions
toJSON: ->
attributes = _.clone(@attributes)
attributes.questions = attributes.questions.toJSON()
attributes
Then I can do stuff like:
survey = Survey.get("my-id")
survey.questions.at(0).title = "First question"
survey.questions.at(1).title = "Second question"
survey.save()
Which works quite comfortable.
you can most certainly have a questionsCollection inside your SurveyModel.
but you are right on the question being seen as a single question (since every question should have his own ID, it might still be possible to know on the server which survey it belongs to...)
then there is the parsing of your json: if you are building your collection's and models manually you won't have this issue, but if you would add your nested JSON it will not automatically create a sub collection to your model. you would need to specify such things in an overridden parse method.
I think you can even do that at contruction
class Survey extends Backbone.Model
initialize: ->
@questions = new QuestionsCollection(@get('questions'))
Also you can extend model universally to get the nested data:
_.extend Backbone.Model::, deepToJSON: ->
obj = @toJSON()
_.each _.keys(obj), (key) ->
obj[key] = obj[key].deepToJSON() if _.isFunction(obj[key].deepToJSON)
obj
_.extend Backbone.Collection::, deepToJSON: ->
@map (model) ->
model.deepToJSON()
Overriding parse/toJSON is a workable solution. One gotcha to be aware of though is that "this" in the parse method is not the model object when the object is fetched via a collection. What happens then is that parse is invoked and the result is passed to initialize. The reason you may need "this" to point to the model object is if you want to bind events on the collection. An alternative approach is to override the set method instead. I put up a simple script on Github that showcases this approach.