从第三方填充模型 API
Populating model from a third-party API
我正在使用 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
的实例并将其添加到 MyModel2
中的 MyModels
。
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 填充模型实例的正确方法吗?我正在尝试找出此错误的原因。
如果没有更完整的信息很难说,但看起来你在保存时没有正确设置上下文 MyModels
:
this.myModels.on('change', this.save);
这是 on()
方法的可选最后一个参数,所以可能:
this.myModels.on('change', this.save, this);
而,他只关注最可能的错误,而让您处理其他所有事情。我会在我的回答中尝试对此进行扩展。
模型 URL 在查询字符串中有 ID
API的URL非常复杂,每次需要的时候都复制粘贴很麻烦。最好在一个地方进行 URL 处理,实现此目的的一种方法是使用简单的 service.
// 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>
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 gives structure to web applications by providing models
with key-value binding and custom events, collections with a rich API
of enumerable functions, views with declarative event handling, and
connects it all to your existing API over a RESTful JSON interface.
如果您使用的 API 遵守 REST 原则,则可能有一个 returns 对象数组的端点。这是集合应该获取其数据的地方。
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
is the newer and better option because these listeners will
be automatically removed for you during stopListening
which is called
when a view gets removed (via remove()
). Prior to listenTo
there was a
really insidious problem with phantom views hanging around forever
(leaking memory and causing misbehavior)...
避免使用 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...
});
创建新模型并获取它在 Backbone 设计中没有任何意义,除非您将 id 传递给模型。只需使用空对象调用 add
on the collection 即可创建默认模型。
我正在使用 Backbone 进行个人项目,我在其中创建了一个名为 MyModel
的模型。在初始化这个模型时,我想从来自第三方 API:
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
的实例并将其添加到 MyModel2
中的 MyModels
。
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 填充模型实例的正确方法吗?我正在尝试找出此错误的原因。
如果没有更完整的信息很难说,但看起来你在保存时没有正确设置上下文 MyModels
:
this.myModels.on('change', this.save);
这是 on()
方法的可选最后一个参数,所以可能:
this.myModels.on('change', this.save, this);
而
模型 URL 在查询字符串中有 ID
API的URL非常复杂,每次需要的时候都复制粘贴很麻烦。最好在一个地方进行 URL 处理,实现此目的的一种方法是使用简单的 service.
// 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>
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 gives structure to web applications by providing models with key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connects it all to your existing API over a RESTful JSON interface.
如果您使用的 API 遵守 REST 原则,则可能有一个 returns 对象数组的端点。这是集合应该获取其数据的地方。
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
is the newer and better option because these listeners will be automatically removed for you duringstopListening
which is called when a view gets removed (viaremove()
). Prior tolistenTo
there was a really insidious problem with phantom views hanging around forever (leaking memory and causing misbehavior)...
避免使用 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...
});
创建新模型并获取它在 Backbone 设计中没有任何意义,除非您将 id 传递给模型。只需使用空对象调用 add
on the collection 即可创建默认模型。