From d1e9dce7053796b0f47f79f8750e302d0aa45079 Mon Sep 17 00:00:00 2001 From: Jmeas Date: Tue, 6 Jan 2015 20:33:55 -0500 Subject: [PATCH] Refactor serializeData to no longer use toJSON. 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 --- api/item-view.yaml | 19 ++++++++++++---- docs/marionette.itemview.md | 44 ++++++++++++++++++++++--------------- docs/marionette.view.md | 9 +++++++- src/composite-view.js | 16 +++----------- src/item-view.js | 41 ++++++++++++++++------------------ src/view.js | 11 ++++++---- test/unit/item-view.spec.js | 12 ++-------- test/unit/view.spec.js | 6 +++-- 8 files changed, 84 insertions(+), 74 deletions(-) diff --git a/api/item-view.yaml b/api/item-view.yaml index a2ed44edc2..3c4027af5b 100644 --- a/api/item-view.yaml +++ b/api/item-view.yaml @@ -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 @@ -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; @@ -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. @@ -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. diff --git a/docs/marionette.itemview.md b/docs/marionette.itemview.md index 5498715367..7cb00561d1 100644 --- a/docs/marionette.itemview.md +++ b/docs/marionette.itemview.md @@ -225,13 +225,29 @@ 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"}); @@ -272,19 +288,11 @@ MyItemView.render(); ``` -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 -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 diff --git a/docs/marionette.view.md b/docs/marionette.view.md index 6503cc3b48..399e7ff264 100644 --- a/docs/marionette.view.md +++ b/docs/marionette.view.md @@ -403,7 +403,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 diff --git a/src/composite-view.js b/src/composite-view.js index 7bbc818907..2e64837d20 100644 --- a/src/composite-view.js +++ b/src/composite-view.js @@ -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. @@ -85,9 +77,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'); diff --git a/src/item-view.js b/src/item-view.js index a67351b611..e10966cd5e 100644 --- a/src/item-view.js +++ b/src/item-view.js @@ -12,35 +12,32 @@ 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. + // Serialize the view's model *or* collection, if + // it exists, for the template serializeData: function() { - if (!this.model && !this.collection) { - return {}; - } - - var args = [this.model || this.collection]; - if (arguments.length) { - args.push.apply(args, arguments); - } + var data = {}; + // If we have a model, we serialize that if (this.model) { - return this.serializeModel.apply(this, args); - } else { - return { - items: this.serializeCollection.apply(this, args) + data = this.serializeModel(); + + } else if (this.collection) { + // Otherwise, we serialize the collection, + // making it available under the `items` property + + 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 _.pluck(this.collection.invoke('clone'), 'attributes'); }, // Render the view, defaulting to underscore.js templates. diff --git a/src/view.js b/src/view.js index 5c534437c7..48419ac76e 100644 --- a/src/view.js +++ b/src/view.js @@ -34,10 +34,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 diff --git a/test/unit/item-view.spec.js b/test/unit/item-view.spec.js index 8ac30c4daa..9ba24af5b4 100644 --- a/test/unit/item-view.spec.js +++ b/test/unit/item-view.spec.js @@ -377,17 +377,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; }); @@ -397,17 +393,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; }); diff --git a/test/unit/view.spec.js b/test/unit/view.spec.js index 7083f2dc54..cbaa461f43 100644 --- a/test/unit/view.spec.js +++ b/test/unit/view.spec.js @@ -273,11 +273,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); }); });