如何在Backbone.js中保存整个集合 - 使用Backbone.sync还是jQuery.ajax?

81

我完全知道可以这样做,并且已经在多个地方查看了相关信息(包括:保存整个集合的最佳实践?)。但是我仍不清楚代码中“确切的如何”?(该帖子用英语解释了。如果有一个特定于javascript的解释将很好:)

假设我有一组模型 - 模型本身可能具有嵌套集合。我已经重写了父集合的toJSON()方法,并获得了一个有效的JSON对象。我希望“保存”整个集合(对应的JSON),但backbone似乎没有内置该功能。

var MyCollection = Backbone.Collection.extend({
model:MyModel,

//something to save?
save: function() {
   //what to write here?
 }

});

我知道有些地方你需要这样说:

Backbone.sync = function(method, model, options){
/*
 * What goes in here?? If at all anything needs to be done?
 * Where to declare this in the program? And how is it called?
 */
}

一旦“视图”完成处理,它负责告诉集合在服务器上“保存”自己(能够处理批量更新/创建请求)。

出现的问题:

  1. 如何/在代码中写什么来“将其全部连接在一起”?
  2. 回调函数的正确位置是什么,如何指定“成功/错误”回调函数?我的意思是语法上的问题。我不清楚在 Backbone 中注册回调函数的语法……

如果这确实是一个棘手的工作,那么我们可以在视图内调用 jQuery.ajax 并将 this.successMethodthis.errorMethod 作为成功/错误回调吗?这样行得通吗?

我需要与 Backbone 的思维方式保持同步 - 我知道我肯定在与整个集合同步方面缺少某些东西。


你的服务器端代码能否将其作为单个请求处理?换句话说,整个顶级集合、所有模型和嵌套集合作为一个单独的JSON数据包?还是需要逐个保存每个模型?编辑:啊,仔细阅读后,服务器确实能够进行“批量更新/创建”。 - Edward M Smith
@Edward:是的!我已经明确说明了,因为这通常是一个关注点,但在这种情况下不是 :) - PhD
那么,服务器希望接收的数据结构是什么? - Edward M Smith
@Edward:数据结构是否重要?在评论中无法进行格式化,但是它的格式如下:[{postId: 1, labels:[{id:1,name:"a"},{id:2,name:"b"}]}] 基本上每个“postId”都可以有一组/数组标签,它们本身就是对象。可能会有许多这样的帖子...我认为数据格式与手头的问题没有任何关系,除非我漏掉了什么。 - PhD
请参阅 https://dev59.com/Q1zUa4cB1Zd3GeqP6s_f?rq=1 - philfreo
11个回答

64

我的第一反应不是在Backbone.Collection的save方法上覆盖原有的函数,而是将该集合包装到另一个Backbone.Model中,并覆盖该模型的toJSON方法。这样,Backbone.js会将该模型视为单个资源,您就不必过度修改backone的处理方式。

请注意,Backbone.Collection有一个toJSON方法,因此大部分工作已经完成了。您只需要代理包装的Backbone.Model的toJSON方法到Backbone.collection即可。

var MyCollectionWrapper = Backbone.Model.extend({
url: "/bulkupload",

//something to save?
toJSON: function() {
    return this.model.toJSON(); // where model is the collection class YOU defined above
 }

});

3
我查看了源代码,似乎集合没有一个“save”方法,所以覆盖它不应该成为一个问题(如果它确实有一个“save”方法,那么这个世界会变得容易很多 :))。 - PhD
+1 对于包装器的想法 - 简洁而优美。我根本没有想到。我在考虑,也许我需要直接调用 Backboard.sync 并将集合放在“model”参数的位置。不过需要为模型指定一个 URL 才能使其工作...你有什么想法吗?由于同步方法在内部只是在模型参数上调用 getURL(model),并且也不执行任何 typeof 比较...这似乎是有意设计的。 - PhD
3
同意 - 非常优雅。然而,我在使用这个解决方案时遇到了一个问题:我的包装模型总是新的,因此当我保存()它时,它总是一个POST请求。我已经检索到了我的数据,所以当我保存()它时应该是PUT请求。我想我可以硬编码isNew() = false,或者设置一个虚假的ID,但这似乎不是一个优雅的解决方案。你有什么建议吗? - Scott Switzer
1
非常干净的答案,很想看看您如何实例化CollectionWrapper。 - Anthony
是的,这对我也起作用了,加一分为好的努力,但如果我想将两个集合发送到服务器,该怎么做呢? - Joe
显示剩余2条评论

25

一个非常简单的例子,演示如何使用HTML和CSS创建基本的网页布局。

Backbone.Collection.prototype.save = function (options) {
    Backbone.sync("create", this, options);
};

...会给你的集合添加一个 save 方法。请注意,这将始终将集合的所有模型发布到服务器,而不考虑哪些已更改。选项只是常规的 jQuery ajax 选项。


4
看起来没问题。也许加上 return Backbone.sync.. 更符合 Backbone 的风格。 - yves amsellem
我认为 - 对于这种类型 - 更新比创建更好... 顺便说一下,您可以覆盖模型或集合的toJSON,以便您可以调节要发送到服务器的内容...(通常只需要id属性)在Backbone.Relational中,您还可以设置要添加到json格式中的内容。 - inf3rno
1
Backbone.sync 期望使用 "create",请参见 http://backbonejs.org/docs/backbone.html#section-141。 - hacklikecrack

8
我最终采用了类似于“保存”的方法,然后在其中调用了$.ajax。这样我就能更好地控制它,而不需要像@brandgonesurfing所建议的那样添加包装类(虽然我非常喜欢这个想法 :) 正如我所提到的,由于我已经覆盖了collection.toJSON()方法,所以我只需在ajax调用中使用它即可...

希望这能帮助遇到此问题的人...


3
你最好调用Backbone.ajax(它会代理到jQuery,但使代码更易维护)。 - brettwhiteman

5

这真的取决于客户端和服务器之间的合同。以下是一个简化的CoffeeScript示例,其中PUT到/parent/:parent_id/children{"children":[{child1},{child2}]}将替换父级的子项,并返回{"children":[{child1},{child2}]}

class ChildElementCollection extends Backbone.Collection
  model: Backbone.Model
  initialize: ->
    @bind 'add', (model) -> model.set('parent_id', @parent.id)

  url: -> "#{@parent.url()}/children" # let's say that @parent.url() == '/parent/1'
  save: ->
    response = Backbone.sync('update', @, url: @url(), contentType: 'application/json', data: JSON.stringify(children: @toJSON()))
    response.done (models) => @reset models.children
    return response

这是一个相当简单的例子,你可以做更多的事情...这取决于在执行save()时你的数据处于什么状态,需要将其转换为何种状态才能发送到服务器以及服务器返回的内容。

如果你的服务器接受 [{child1},{child2}] 的PUT请求,那么你的Backbone.sync行可以改为 response = Backbone.sync('update', @toJSON(), url: @url(), contentType: 'application/json')


这里不需要选项"url"的属性 =) - Sergey Kamardin

5
答案取决于您在服务器端想要对集合进行什么操作。
如果您需要通过post发送附加数据,则可能需要使用包装器模型或关系模型。
使用包装器模型时,您始终需要编写自己的解析方法。
var Occupants = Backbone.Collection.extend({
    model: Person
});

var House = Backbone.Model.extend({
    url: function (){
        return "/house/"+this.id;
    },
    parse: function(response){
        response.occupants = new Occupants(response.occupants)
        return response;
    }
});

关系模型更好,因为你可以更容易地进行配置,并可以通过includeInJSON选项来调节将哪些属性放入您发送到 REST 服务的 JSON 中。

var House = Backbone.RelationalModel.extend({
    url: function (){
        return "/house/"+this.id;
    },
    relations: [
        {
            type: Backbone.HasMany,
            key: 'occupants',
            relatedModel: Person,
            includeInJSON: ["id"],
            reverseRelation: {
                key: 'livesIn'
            }
        }
    ]
});

如果您不发送其他数据,则可以同步集合本身。在这种情况下,您必须向集合(或集合原型)添加保存方法:

var Occupants = Backbone.Collection.extend({
    url: "/concrete-house/occupants",
    model: Person,
    save: function (options) {
        this.sync("update", this, options);
    }
});

4

虽然这是一个旧帖,但我最终采取的做法如下:

Backbone.Collection.prototype.save = function (options) {
            // create a tmp collection, with the changed models, and the url
            var tmpCollection = new Backbone.Collection( this.changed() );
            tmpCollection.url = this.url;
            // sync
            Backbone.sync("create", tmpCollection, options);
        };
        Backbone.Collection.prototype.changed = function (options) {
            // return only the changed models.
            return this.models.filter( function(m){
                return m.hasChanged()
            });
        };
// and sync the diffs.
self.userCollection.save();

相当简单直接 :)

3

我也很惊讶Backbone集合没有内置的保存功能。这是我在我的backbone集合上添加的代码来实现它。我绝对不想单独迭代每个模型并进行保存。此外,我正在使用Node上的Backbone后端,因此覆盖了本机Backbone.sync以保存到我的小项目上的平面文件,但代码应该基本相同:

    save: function(){                                                                                                                                                                                                                                                                                                                                                     
      Backbone.sync('save', this, {                                                                                                                                                                                                                                                                                                                                     
        success: function(){                                                                                                                                                                                                                                                                                                                                          
          console.log('users saved!');                                                                                                                                                                                                                                                                                                                              
        }                                                                                                                                                                                                                                                                                                                                                             
      });                                                                                                                                                                                                                                                                                                                                                               
    }

也可以只传递选项,例如 save: function (options) { Backbone.sync('save', this, options); } - Matt Fletcher

2
这是一个简单的例子:
var Books = Backbone.Collection.extend({
model: Book,
url: function() {
  return '/books/';
},
save: function(){
  Backbone.sync('create', this, {
    success: function() {
      console.log('Saved!');
    }
  });
 }
});

当您在集合上调用save()方法时,它会向定义的URL发送PUT方法请求。

我想知道你是否能帮助解决我的集合保存问题,现在http://jsfiddle.net/kyllle/f1h4cz7f/3/ toJSON()更新了每个模型,但是保存似乎没有任何数据? - styler

1

这个被接受的答案已经很好了,但我可以更进一步地为您提供代码,确保适当的事件被触发到您的监听器,同时还允许您传递选项ajax事件回调函数:

save: function( options ) {
  var self = this;

  var success = options.success;
  var error = options.error;
  var complete = options.complete;

  options.success = function( response, status, xhr ) {
    self.trigger('sync', self, response, options);
    if (success) return success.apply(this, arguments);
  };

  options.error = function( response, status, xhr ) {
    self.trigger('error', self, response, options);
    if (error) return error.apply(this, arguments);
  };

  options.complete = function( response, status, xhr ) {
    if (complete) return complete.apply(this, arguments);
  }

  Backbone.sync('create', this, options);
}

1
我会尝试这样做:
var CollectionSync = function(method, model, [options]) {
    // do similar things to Backbone.sync
}

var MyCollection = Backbone.Collection.extend({
    sync: CollectionSync,
    model: MyModel,
    getChanged: function() {
        // return a list of models that have changed by checking hasChanged()
    },
    save: function(attributes, options) {
        // do similar things as Model.save
    }
});

( https://dev59.com/Pm445IYBdhLWcg3wJ298#11085198 )

https://dev59.com/Pm445IYBdhLWcg3wJ298#11085198


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接