Skip to content

Commit

Permalink
Refactor serializeData to no longer use toJSON.
Browse files Browse the repository at this point in the history
toJSON should only be used for preparing data to be sent to the
server. In addition, serializeData no longer accepts arguments,
distinguishing it further from templateHelpers.

Resolves #1476
  • Loading branch information
jamesplease committed Jan 7, 2015
1 parent 26454ed commit 2447ea6
Show file tree
Hide file tree
Showing 9 changed files with 87 additions and 75 deletions.
19 changes: 15 additions & 4 deletions api/item-view.yaml
Expand Up @@ -40,9 +40,14 @@ constructor:
functions:
serializeData:
description: |
Serialize the model or collection for the view. If a model is found, the view's `serializeModel` is called. If a collection is found, each model in the collection is serialized by calling the view's `serializeCollection` and putting into an `items` array in the resulting data. If both are found, the model is used.
Serialize the view's model or collection for rendering the view's template. If a model is found, the view's `serializeModel` is called. If a
collection is found, `serializeCollection` will be called. The collection will be available in your template as `items`. If both a model and a
collection are found, the model will be used.
You can override the `serializeData` method in your own view definition, to provide custom serialization for your view's data. These serializations are then passed into the template function if one is set.
You can override the `serializeData` method in your own view definition to provide custom serialization for your view's `model` or
`collection`.
Do not override `serializeData` to add additional data to your templates. Use `templateHelpers` for that instead.
@api public
Expand All @@ -59,10 +64,12 @@ functions:
var data = {};
if (this.collection) {
data = { items: _.partial(this.serializeCollection, this.collection).apply(this, arguments) };
data = {
items: this.serializeCollection()
};
}
else if (this.model) {
data = _.partial(this.serializeModel, this.model).apply(this, arguments);
data = this.serializeModel();
}
return data;
Expand All @@ -74,6 +81,8 @@ functions:
description: |
Serialize the view's model.
Do not override `serializeModel` to add additional data to your templates. Use `templateHelpers` for that instead.
@api public
@param {Backbone.Model} model - The model set on the ItemView to be serialized.
@returns {Object} Javascript object representation of the model.
Expand All @@ -97,6 +106,8 @@ functions:
description: |
Serialize the view's collection.
Do not override `serializeCollection` to add additional data to your templates. Use `templateHelpers` for that instead.
@api public
@param {Backbone.Collection} collection - The collection set on the ItemView to be serialized.
Expand Down
44 changes: 26 additions & 18 deletions docs/marionette.itemview.md
Expand Up @@ -225,13 +225,29 @@ Backbone.Marionette.ItemView.extend({

## ItemView serializeData

Item views will serialize a model or collection, by default, by
calling `.toJSON` on either the model or collection. If both a model
and collection are attached to an item view, the model will be used
as the data source. The results of the data serialization will be passed to the template
that is rendered.
This method is used to convert a View's `model` or `collection`
into a usable form for a template.

If the serialization is a model, the results are passed in directly:
Item Views are called such because they process only a single item
at a time. Consequently, only the `model` **or** the `collection` will
be serialized. If both exist, only the `model` will be serialized.

By default, models are serialized by cloning the attributes of the model.

Collections are serialized into an object of this form:

```js
{
items: [modelOne, modelTwo]
}
``

where each model in the collection will have its attributes cloned.

The result of `serializeData` is included in the data passed to
the view's template.
Let's take a look at some examples of how serializing data works.

```js
var myModel = new MyModel({foo: "bar"});
Expand Down Expand Up @@ -272,19 +288,11 @@ MyItemView.render();
</script>
```

If you need custom serialization for your data, you can provide a
`serializeData` method on your view. It must return a valid JSON
object, as if you had called `.toJSON` on a model or collection.
If you need to serialize the View's `model` or `collection` in a custom way,
then you should override either `serializeModel` or `serializeCollection`.
```js
Backbone.Marionette.ItemView.extend({
serializeData: function(){
return {
"some attribute": "some value"
}
}
});
```
On the other hand, you should not use this method to add arbitrary extra data
to your template. Instead, use [View.templateHelpers](./marionette.view.md#viewtemplatehelpers).
## Organizing UI Elements
Expand Down
9 changes: 8 additions & 1 deletion docs/marionette.view.md
Expand Up @@ -395,7 +395,14 @@ This works for both `modelEvents` and `collectionEvents`.

## View.serializeModel

The `serializeModel` method will serialize a model that is passed in as an argument.
This method is used internally during a view's rendering phase. It
will serialize the View's `model` property, adding it to the data
that is ultimately passed to the template.

If you would like to serialize the View's `model` in a special way,
then you should override this method. With that said, **do not** override
this if you're simply adding additional data to your template, like computed
fields. Use [templateHelpers](#viewtemplatehelpers) instead.

## View.bindUIElements

Expand Down
16 changes: 3 additions & 13 deletions src/composite-view.js
Expand Up @@ -46,17 +46,9 @@ Marionette.CompositeView = Marionette.CollectionView.extend({
return childView;
},

// Serialize the model for the view.
// You can override the `serializeData` method in your own view
// definition, to provide custom serialization for your view's data.
// Return the serialized model
serializeData: function() {
var data = {};

if (this.model){
data = _.partial(this.serializeModel, this.model).apply(this, arguments);
}

return data;
return this.serializeModel();
},

// Renders the model and the collection.
Expand All @@ -83,9 +75,7 @@ Marionette.CompositeView = Marionette.CollectionView.extend({
// Render the root template that the children
// views are appended to
_renderTemplate: function() {
var data = {};
data = this.serializeData();
data = this.mixinTemplateHelpers(data);
var data = this.mixinTemplateHelpers(this.serializeData());

this.triggerMethod('before:render:template');

Expand Down
43 changes: 21 additions & 22 deletions src/item-view.js
Expand Up @@ -12,35 +12,34 @@ Marionette.ItemView = Marionette.View.extend({
Marionette.View.apply(this, arguments);
},

// Serialize the model or collection for the view. If a model is
// found, the view's `serializeModel` is called. If a collection is found,
// each model in the collection is serialized by calling
// the view's `serializeCollection` and put into an `items` array in
// the resulting data. If both are found, defaults to the model.
// You can override the `serializeData` method in your own view definition,
// to provide custom serialization for your view's data.
serializeData: function(){
if (!this.model && !this.collection) {
return {};
}
// Serialize the view's model *or* collection, if
// it exists, for the template
serializeData: function() {
var data = {};

var args = [this.model || this.collection];
if (arguments.length) {
args.push.apply(args, arguments);
// If we have a model, we serialize that
if (this.model) {
data = this.serializeModel();
}

if (this.model) {
return this.serializeModel.apply(this, args);
} else {
return {
items: this.serializeCollection.apply(this, args)
// Otherwise, we serialize the collection,
// making it available under the `items` property
else if (this.collection) {
data = {
items: this.serializeCollection()
};
}

return data;
},

// Serialize a collection by serializing each of its models.
serializeCollection: function(collection){
return collection.toJSON.apply(collection, _.rest(arguments));
// Serialize a collection by cloning each of
// its model's attributes
serializeCollection: function() {
if (!this.collection) { return {}; }
return this.collection.map(function(model) {
return _.clone(model.attributes);
});
},

// Render the view, defaulting to underscore.js templates.
Expand Down
11 changes: 7 additions & 4 deletions src/view.js
Expand Up @@ -32,10 +32,13 @@ Marionette.View = Backbone.View.extend({
return this.getOption('template');
},

// Serialize a model by returning its attributes. Clones
// the attributes to allow modification.
serializeModel: function(model){
return model.toJSON.apply(model, _.rest(arguments));
// Prepares the special `model` property of a view
// for being displayed in the template. By default
// we simply clone the attributes. Override this if
// you need a custom transformation for your view's model
serializeModel: function() {
if (!this.model) { return {}; }
return _.clone(this.model.attributes);
},

// Mix in template helper methods. Looks for a
Expand Down
2 changes: 1 addition & 1 deletion test/unit/composite-view.spec.js
Expand Up @@ -790,7 +790,7 @@ describe('composite view', function() {
this.view.serializeData();
});

it("should call serializeModel", function(){
it("should call serializeModel", function() {
expect(this.view.serializeModel).to.have.been.calledOnce;
});
});
Expand Down
12 changes: 2 additions & 10 deletions test/unit/item-view.spec.js
Expand Up @@ -310,17 +310,13 @@ describe('item view', function() {
describe('and the view only has a collection', function() {
beforeEach(function() {
this.itemView.collection = new Backbone.Collection(this.collectionData);
this.itemView.serializeData(1, 2, 3);
this.itemView.serializeData();
});

it("should call serializeCollection", function(){
expect(this.itemView.serializeCollection).to.have.been.calledOnce;
});

it('and the serialize function should be called with the provided arguments', function() {
expect(this.itemView.serializeCollection).to.have.been.calledWith(this.itemView.collection, 1, 2, 3);
});

it("should not call serializeModel", function() {
expect(this.itemView.serializeModel).to.not.have.been.called;
});
Expand All @@ -330,17 +326,13 @@ describe('item view', function() {
beforeEach(function() {
this.itemView.model = new Backbone.Model(this.modelData);
this.itemView.collection = new Backbone.Collection(this.collectionData);
this.itemView.serializeData(1, 2, 3);
this.itemView.serializeData();
});

it("should call serializeModel", function() {
expect(this.itemView.serializeModel).to.have.been.calledOnce;
});

it('and the serialize function should be called with the provided arguments', function() {
expect(this.itemView.serializeModel).to.have.been.calledWith(this.itemView.model, 1, 2, 3);
});

it('should not call serializeCollection', function() {
expect(this.itemView.serializeCollection).to.not.have.been.called;
});
Expand Down
6 changes: 4 additions & 2 deletions test/unit/view.spec.js
Expand Up @@ -220,11 +220,13 @@ describe('base view', function() {

beforeEach(function(){
model = new Backbone.Model(modelData);
view = new Marionette.View();
view = new Marionette.View({
model: model
});
});

it("should return all attributes", function(){
expect(view.serializeModel(model)).to.be.eql(modelData);
expect(view.serializeModel()).to.be.eql(modelData);
});
});
});

0 comments on commit 2447ea6

Please sign in to comment.