从第三方API填充模型

时间:2016-12-26 18:55:57

标签: javascript backbone.js backbone-views backbone-collections

我使用Backbone作为个人项目,我在其中创建了一个名为MyModel的模型。在初始化此模型时,我想从第三方API的JSON响应中填充其属性:

app.MyModel = Backbone.Model.extend({
      url: 'https://api.xxxxxx.com/v12_1/item?id=53444d0d7ba4ca15456f5690&appId=xxxx&appKey=yyyy',

      defaults: {
          name: 'Default Name'
      }

  });

此模型用于将在另一个模型中嵌入的属性中使用的集合中:

app.MyModels = Backbone.Collection.extend({
    model: app.MyModel
});

app.MyModel2 = Backbone.Model.extend({

    // Default attributes
    defaults: {
        name: 'Default Name'
    },

    initialize: function() {
        this.myModels = new app.MyModels();
        this.myModels.on('change', this.save);
    }
});

在为MyModel2创建的视图中,我向全局元素添加了一个侦听器,以便我们可以在MyModel内初始化并将MyModels的实例添加到MyModel2

app.MyModel2View = Backbone.View.extend({

    initialize: function() {
        // ...some code...
        var self = this;
        this.$(".add-myModel").click(function() {
            var myModel = new app.MyModel();
            myModel.fetch();
            self.model.myModels.add(myModel);
        });
        // ...some code...
    },
    // ...some code...
});

这实际上是在实现预期目标,但在单击元素并添加实例时在控制台中抛出错误:

backbone.js:646 Uncaught TypeError: this.isNew is not a function

这是Backbone中从外部API填充模型实例的正确方法吗?我试图弄清楚这个错误的原因。

2 个答案:

答案 0 :(得分:2)

很难说没有更完整的信息,但在保存MyModels时,您似乎没有正确设置上下文:

this.myModels.on('change', this.save);

这是on()方法的可选最后一个参数,所以可能:

this.myModels.on('change', this.save, this);

请参阅documentation

中的详细信息

答案 1 :(得分:1)

虽然Stephen is right,但他只关注最可能的错误并让你处理其他一切。我将在答案中尝试对此进行扩展。

查询字符串中包含ID的模型URL

API的网址非常复杂,每次需要时复制粘贴都很麻烦。最好将URL处理放在一个地方,一种方法是使用简单的服务

// The API service to use everywhere you need the API specific data.
app.API = {
    protocol: 'https',
    domain: 'api.xxxxxx.com',
    root: '/v12_1/',
    params: {
        appId: 'xxxx',
        appKey: 'yyyy',
    },
    /**
     * Get the full API url, with your optional path.
     * @param  {String} path (optional) to add to the url.
     * @return {String}  full API url with protocol, domain, root.
     */
    url: function(path) {
        path = path || '';
        if (path.slice(-1) !== '/') path += '/';
        return this.protocol + "://" + this.domain + this.root + path;
    },
    /**
     * Adds the query string to the url, merged with the default API parameters.
     * @param  {String} url  (optional) before the query string
     * @param  {Object} params to transform into a query string
     * @return {String}   e.g.: "your-url?param=value&otherparam=123"
     */
    applyParams: function(url, params) {
        return (url || "") + "?" + $.param(_.extend({}, this.params, params));
    },
};

填写API信息。

然后,您可以创建基本模型和集合(或替换默认的Backbone行为)。

app.BaseModel = Backbone.Model.extend({
    setId: function(id, options) {
        return this.set(this.idAttribute, id, options);
    },
    url: function() {
        var base =
            _.result(this, 'urlRoot') ||
            _.result(this.collection, 'url') ||
            urlError();
        var id = this.get(this.idAttribute);
        return app.API.applyParams(base, this.isNew() || { id: encodeURIComponent(id) });
    },
});

app.BaseCollection = Backbone.Collection.extend({
    model: app.BaseModel,
    sync: function(method, collection, options) {
        var url = options.url || _.result(model, 'url') || urlError();
        options.url = aop.API.applyParams(url);
        return app.BaseCollection.__super__.sync.apply(this, arguments);
    }
});

然后使用它就像这样简单:

app.MyModel = app.BaseModel.extend({
    urlRoot: app.API.url('item'),
})

app.Collection = app.BaseCollection.extend({
    model: app.MyModel,
    url: app.API.url('collection-items'),
});

以下测试输出:



var app = app || {};
(function() {


  app.API = {
    protocol: 'https',
    domain: 'api.xxxxxx.com',
    root: '/v12_1/',
    params: {
      appId: 'xxxx',
      appKey: 'yyyy',
    },
    /**
     * Get the full API url, with your optional path.
     * @param  {String} path (optional) to add to the url.
     * @return {String}  full API url with protocol, domain, root.
     */
    url: function(path) {
      path = path || '';
      if (path.slice(-1) !== '/') path += '/';
      return this.protocol + "://" + this.domain + this.root + path;
    },
    /**
     * Adds the query string to the url, merged with the default API parameters.
     * @param  {String} url  (optional) before the query string
     * @param  {Object} params to transform into a query string
     * @return {String}   e.g.: "your-url?param=value&otherparam=123"
     */
    applyParams: function(url, params) {
      return (url || "") + "?" + $.param(_.extend({}, this.params, params));
    },
  };

  app.BaseModel = Backbone.Model.extend({
    setId: function(id, options) {
      return this.set(this.idAttribute, id, options);
    },
    url: function() {
      var base =
        _.result(this, 'urlRoot') ||
        _.result(this.collection, 'url') ||
        urlError();
      var id = this.get(this.idAttribute);
      return app.API.applyParams(base, this.isNew() || {
        id: encodeURIComponent(id)
      });
    },
  });

  app.BaseCollection = Backbone.Collection.extend({
    model: app.BaseModel,
    sync: function(method, collection, options) {
      var url = options.url || _.result(model, 'url') || urlError();
      options.url = aop.API.applyParams(url);
      return app.BaseCollection.__super__.sync.apply(this, arguments);
    }
  });

  app.MyModel = app.BaseModel.extend({
    urlRoot: app.API.url('item'),
  })

  app.Collection = app.BaseCollection.extend({
    model: app.MyModel,
    url: app.API.url('collection-items'),
  });

  var model = new app.MyModel();
  console.log("New model url:", model.url());
  model.setId("53444d0d7ba4ca15456f5690");
  console.log("Existing model url:", model.url());

  var collection = new app.Collection();
  console.log("collection url:", _.result(collection, 'url'));

  var modelUrlThroughCollection = new app.BaseModel({
    id: "test1234"
  });
  collection.add(modelUrlThroughCollection);
  console.log("model via collection:", modelUrlThroughCollection.url());
})();

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
&#13;
&#13;
&#13;

New model url: https://api.xxxxxx.com/v12_1/item/?appId=xxxx&appKey=yyyy
Existing model url: https://api.xxxxxx.com/v12_1/item/?appId=xxxx&appKey=yyyy&id=53444d0d7ba4ca15456f5690
collection url: https://api.xxxxxx.com/v12_1/collection-items/
model via collection: https://api.xxxxxx.com/v12_1/collection-items/?appId=xxxx&appKey=yyyy&id=test1234

如何使用外部API填充模型?

  

Backbone.js通过提供模型为Web应用程序提供结构   使用键值绑定和自定义事件,具有丰富API的集合   可枚举函数,具有声明性事件处理的视图,以及   通过RESTful JSON界面将其全部连接到现有API

如果您使用的API遵循REST原则,则可能是返回对象数组的端点。这是集合应该获取其数据的地方。

app.Collection = app.BaseCollection.extend({
    model: app.MyModel,
    url: app.API.url('collection-items'),
});
var collection = new app.Collection();
// GET request to 
// https://api.xxxxxx.com/v12_1/collection-items/?appId=xxxx&appKey=yyyy
collection.fetch();

它应该收到类似的东西:

[
    { id: "24b6463n5", /* ... */ },
    { id: "345333bbv", /* ... */ },
    { id: "3g6g346g4", /* ... */ },
    /* ... */
]

如果要将现有模型(使用ID引用)添加到集合中:

var model = new app.MyModel({
    // giving an id to a model will make call to fetch possible
    id: "53444d0d7ba4ca15456f5690" 
});

// GET request to 
// https://api.xxxxxx.com/v12_1/item/?appId=xxxx&appKey=yyyy&id=53444d0d7ba4ca15456f5690
model.fetch();
collection.add(model);

响应应该是单个对象:

{ id: "53444d0d7ba4ca15456f5690", /* ... */ }

如果您想创建新模型:

var model = new app.MyModel({ test: "data", /* notice no id passed */ });
// POST request to
// https://api.xxxxxx.com/v12_1/item/?appId=xxxx&appKey=yyyy
model.save();
// or, equivalent using a collection:
collection.create({ test: "data", /* notice no id passed */ });

避免.on / .bind支持.listenTo

在Backbone上传递事件绑定的上下文非常重要,因为大多数部分是而不是jQuery回调,它们通常是处理局部变量的匿名函数。除此之外,您还应使用Backbone's listenTo代替on

Backbone js .listenTo vs .on

  

listenTo是更新更好的选择,因为这些听众会   在被叫stopListening期间自动删除   删除视图时(通过remove())。在listenTo之前有一个   真正阴险的问题与幽灵般的观点永远存在   (泄漏记忆并造成不良行为)......

避免使用jQuery手动绑定事件

在视图中,您应该使用events property自动将DOM事件委托给视图的回调。它仍然是jQuery的背景,但更干净,已经集成到Backbone中,并且上下文会自动传递,因此不需要使用var self = this技巧。

app.MyModel2View = Backbone.View.extend({
    events: {
        "click .add-myModel": "onAddModelClick",
    },
    onAddModelClick: function() {
        this.model.myModels.add({});
    },
    // ...some code...
});

除非将id传递给模型,否则从Backbone设计创建新模型并获取它是没有意义的。只需使用空对象调用add on the collection即可创建默认模型。