I am getting an error when calling the fetch function.
Undefined is not a function Error in Backbone js
var models = Backbone.Model.extend({
userId: function() {
return this.get('userId');
},
id: function() {
return this.get('id');
},
body: function() {
return this.get('body');
}
});
//Collections
var todolistStore = Backbone.Collection.extend({
url: function() {
return 'https://jsonplaceholder.typicode.com/posts'
},
model: models,
parse: function(response) {
return response;
}
});
todolistStore.fetch();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.cdnjs.com/ajax/libs/underscore.js/1.1.4/underscore-min.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/0.9.2/backbone-min.js" type="text/javascript"></script>
There are a couple of problems. I'm building off of #ChrisG 's answer.
You need to instantiate a collection. Here's what my code:
var Model = Backbone.Model.extend({
userId: function() {
return this.get('userId');
},
id: function() {
return this.get('id');
},
body: function() {
return this.get('body');
}
});
//Collections
var TodoListStore = Backbone.Collection.extend({
url: function() {
return 'https://jsonplaceholder.typicode.com/posts'
},
model: Model,
parse: function(response) {
return response;
}
});
var todolistStore = new TodoListStore();
todolistStore.fetch();
I updated the version of Underscore and Backbone
https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js
https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js
[{"uniqueId":61,"content":"test","createTextDate":"time"}]
this is /data/commentList.json
var Comment = Backbone.Model.extend({
defaults: {
uniqueId: null,
createTextDate: null,
content: null
}
});
Model for Backbone Collection
var List = Backbone.Collection.extend({
model: Comment,
url: function() {
return '/data/commentList.json';
},
parse: function(response) {
return response.results;
},
sync: function(method, model, options) {
var that = this;
var params = _.extend({
type: 'GET',
dataType: 'jsonp',
url: that.url(),
processData: false
}, options);
return $.ajax(params);
}
});
Backbone Collection
var ListView = Backbone.View.extend({
el: $('#test'),
initialize: function() {
_.bindAll(this, 'render');
this.collection = new List();
this.render();
},
render: function() {
var self = this;
this.collection.fetch({
success: function() {
console.log("SUCESS");
console.log(that.collection.toJSON());
},
error: function() {
console.log('Failed to fetch!');
}
});
}
});
Console log
Failed to fetch!
How to make Backbone Collection using jSON url?
parse: function(response) {
return response.results;
},
Above code assumes your server returns
{"results": [comment1, comment2]}
You rarely need to override sync method.
I have a site developed in cakephp where I'm using a simple app in backbone.
Now I would like to save data from backbone but doesn't work, return always inside callback error, and it doens't take the right value to save inside table.
This is my simple app:
TaskModel = Backbone.Model.extend({
url: function(){
return "/step_task/ajax_save";
}
});
TaskCollection = Backbone.Collection.extend({
model: TaskModel,
initData: function(data){
return data;
}
});
var TaskView = Backbone.View.extend({
template: _.template($("#task-template").html()),
initialize: function(){
this.task = new TaskCollection(<?php echo json_encode($step['StepTask']); ?>);
this.render();
},
render: function(){
taskModel = new TaskModel({
'id': '1',
'user_id': '1'
});
//--------------------------- here I would like to save inside my table ----------------
taskModel.save(null, {
success: function(model, response) {
console.log('success');
console.log(model);
console.log(response);
},
error: function(model, response) {
console.log('error');
console.log(model);
console.log(response);
},
wait: true // Add this
});
$(this.el).html(this.template({tasks: this.task.models}));
}
});
and this is my funciton inside StepTaskController.php
public function ajax_save(){
$this->autoRender = false;
$this->StepTask->save($this->request->data);
}
How can I solve it?
Try to change the url in your model to urlRoot :
TaskModel = Backbone.Model.extend({
urlRoot: '/step_task/ajax_save'
});
Some time ago, I setup an abstract data factory in AngularJS, and need a hint on how to simplify its use. I was initially under the impression that I would create another factory per entity that would be called from an AngularJS controller. Turns out it just looks too complicated and I would like to remove the intermediary entity factory (ContentTypesFactory), and simply call the abstract factory directly from the AngularJS controller.
In the following example I'm wiring up a KendoUI datasource.
AbstractRepository.js:
app.factory('abstractRepository', [function () {
// we will inject the $http service as repository service
// however we can later refactor this to use another service
function abstractRepository(repositoryService, whichEntity, odataUrlBase) {
//this.http = $http;
this.http = repositoryService;
this.whichEntity = whichEntity;
this.odataUrlBase = odataUrlBase;
this.route;
}
abstractRepository.prototype = {
getList: function () {
return this.http.get(this.odataUrlBase);
},
get: function (id) {
return this.http.get(this.odataUrlBase + '/' + id);
},
insert: function (entity) {
return this.http.post(this.odataUrlBase, entity);
},
update: function (entity) {
return this.http.put(this.odataUrlBase + '/' + entity.ID, this.whichEntity);
},
remove: function (id) {
return this.http.delete(this.odataUrlBase + '/' + id);
}
};
abstractRepository.extend = function (repository) {
repository.prototype = Object.create(abstractRepository.prototype);
repository.prototype.constructor = repository;
}
return abstractRepository;
}]);
ContentTypesFactory.js:
/// <reference path="../Scripts/angular.min.js" />
/// <reference path="../app.js" />
/// <reference path="AbstractRepository.js" />
// each function returns a promise that can be wired up to callback functions by the caller
// the object returned from the factory is a singleton and can be reused by different controllers
app.factory('contentTypesRepository', ['$http', 'abstractRepository', function ($http, abstractRepository) {
var odataUrlBase = '/odata/ContentTypes'
var whichEntity = 'ContentTypes';
function contentTypesRepository() {
abstractRepository.call(this, $http, whichEntity, odataUrlBase);
}
abstractRepository.extend(contentTypesRepository);
return new contentTypesRepository();
}]);
ContentTypesController.js:
app.controller('contentTypesController', ['$scope', '$log', 'contentTypesRepository',
function ($scope, $log, contentTypesFactory) {
$scope.odataUrlBase = '/odata/ContentTypes';
$scope.status;
//
// Grid
//
$scope.contentTypesDataSource = new kendo.data.HierarchicalDataSource({
type: "odata",
transport: {
create: {
url: $scope.odataUrlBase
},
read: {
type: "GET",
url: $scope.odataUrlBase,
//function (data) {
// pass in the URL to the abstract factory
//},
dataType: "json" // the default result type is JSONP, but WebAPI does not support JSONP
},
update: {
contentType: "application/json",
url: function (data) {
return $scope.odataUrlBase + '(' + data.ContentTypesId + ')';
} . . .
Now, removing the reference to ContentTypesFactory, my new controller code looks like this:
app.controller('contentTypesController', ['http', '$scope', '$log', 'abstractDataFactory',
// the abstract data factory accepts controller type parameters for RESTful CRUD
function ($scope, $log, abstractDataFactory) {
//function ContentTypeController($scope) {
var crudServiceBaseUrl = "/odata/ContentTypes";
var odataUrlBase = '/odata/ContentTypes'
var whichEntity = 'ContentTypes';
// Pulled the following code from the ControlerTypesFactory, but not sure how to implement it in this ContentTypesController:
//function contentTypesRepository() {
// abstractRepository.call(this, $http, whichEntity, odataUrlBase);
//}
//abstractRepository.extend(contentTypesRepository);
//return new contentTypesRepository();
$scope.greeting = 'Hola!';
var dataSource = new kendo.data.DataSource({
type: "odata",
transport: {
read: {
url: crudServiceBaseUrl,
dataType: "json"
},
update: { // PUT
url: function (data) {
console.log(data);
dataType: "json"
return crudServiceBaseUrl + "(" + data.ContentTypeId + ")";
},
error: function (e) {
console.log("error: " + e);
}
}, . . .
I'm sure the change is not that difficult, but I'm fairly new to Angular. How can I wire this up so to use the abstract repository, and looking for any other Angular best-practices and naming suggestions?
Taking a fresh look at this this morning, I believe I have a working solution.
Once again, I wanted to remove the intermediary factory abstraction, and have only one abstract data fetching factory that I could pass in parameters directly from from each controller.
$http is injected into the factory which takes in two parameters whichEntity, odataUrlBase, and are set upon instantiation. The factory exposes methods like getList(odataOptions). It is these options that are required to to be passed along with the requests as part of the query string, and are done so through $http's params.
The new abstract factory is injected into the controller, then instantiated with the required parameters:
app.controller('contentTypeController', ['$scope', '$log', 'abstractFactory3',
function ($scope, $log, abstractFactory3) {
var dataFactory = new abstractFactory3("ContentType", "/odata/ContentType");
Here is the new abstract factory:
app.factory('abstractFactory3', function ($http) {
function abstractFactory3(whichEntity, odataUrlBase) {
this.whichEntity = whichEntity;
this.odataUrlBase = odataUrlBase;
}
abstractFactory3.prototype = {
getList: function (odataOptions) {
//var result = $http({
// url: this.odataUrlBase,
// method: 'GET',
// params: odataParams
//});
return $http.get(this.odataUrlBase, {
params: odataOptions
});
}
};
return abstractFactory3;
});
Since this is being used through through a kendo.data.DataSource, we have to extract the OData parameters:
var odataParams = kendo.data.transports["odata"].parameterMap(options.data, "read");
Upon a successful get, we pass back the data source's options with the result:
var dataSource = new kendo.data.DataSource({
type: "odata",
transport: {
read:
function (options) {
var odataParams = kendo.data.transports["odata"].parameterMap(options.data, "read");
dataFactory.getList(odataParams)
.success(function (result) {
options.success(result);
});
//$http({ // // example of using $http directly ($http injection was removed from this controller, as this is now handled by the abstract data factory)
// url: crudServiceBaseUrl,
// method: 'GET',
// params: odataParams
//})
//.success(function (result) {
// options.success(result);
//});
}, // update, create, destroy, . . .
I realize that in my original question, the controller was in fact using the Kendo transport, rather than the Angular factory that I had setup.
So what I have now is:
angular controller (KendoUI datasource) -> angular data factory
I have also reworded the question in order to more-accurately reflect what the entire problem was, and hope this is of assistance to someone.
There are some other little nuances, like getting and passing data ID's, objects, JSON.stringification that are best shown in a full solution. Hopefully the following helps:
AngularJS abstract data factory:
app.factory('abstractFactory3', function ($http) {
function abstractFactory3(odataUrlBase) {
this.odataUrlBase = odataUrlBase;
}
abstractFactory3.prototype = {
getList: function (odataOptions) {
//var result = $http({
// url: this.odataUrlBase,
// method: 'GET',
// params: odataParams
//});
return $http.get(this.odataUrlBase, {
params: odataOptions
});
},
get: function (id, odataOptions) {
return $http.get(this.odataUrlBase + '/' + id, {
params: odataOptions
});
},
insert: function (data) {
return $http.post(this.odataUrlBase, data);
},
update: function (id, data) {
return $http.put(this.odataUrlBase + '(' + id + ')', data);
},
remove: function (id) {
return $http.delete(this.odataUrlBase + '(' + id + ')');
}
};
return abstractFactory3;
});
AngularJS Controller:
app.controller('contentTypeController', ['$scope', '$log', 'abstractFactory3',
// the abstract data factory accepts controller type parameters for RESTful CRUD
function ($scope, $log, abstractFactory3) {
var dataFactory = new abstractFactory3("/odata/ContentType");
var crudServiceBaseUrl = "/odata/ContentType";
var dataSource = new kendo.data.DataSource({
type: "odata",
transport: {
read:
function (options) {
var odataParams = kendo.data.transports["odata"].parameterMap(options.data, "read");
dataFactory.getList(odataParams)
.success(function (result) {
options.success(result);
})
.error (function (error) {
console.log("data error");
});
},
update:
function (options) {
var data = options.data;
dataFactory.update(data.ContentTypeId, data)
.success(function (result) {
options.success(result);
})
.error(function (error) {
console.log("data error");
});
},
create:
function (options) {
var data = options.data;
data.ContentTypeId = "0"; // required for valid field data
dataFactory.insert(data)
.success(function (result) {
options.success(result);
})
.error(function (error) {
console.log("data error");
});
},
destroy:
function (options) {
var data = options.data;
dataFactory.remove(data.ContentTypeId)
.success(function (result) {
options.success(result);
})
.error(function (error) {
console.log("data error");
});
},
parameterMap: function (options, type) {
// this is optional - if we need to remove any parameters (due to partial OData support in WebAPI
if (operation !== "read" && options.models) {
return JSON.stringify({ models: options });
}
},
},
batch: false,
pageSize: 10,
serverPaging: true,
change: function (e) {
console.log("change: " + e.action);
// do something with e
},
schema: {
data: function (data) {
//console.log(data)
return data.value;
},
total: function (data) {
console.log("count: " + data["odata.count"]);
return data["odata.count"];
},
model: {
id: "ContentTypeId",
fields: {
ContentTypeId: { editable: false, nullable: true },
//UserId: {editable: false, nullable: false },
Description: { type: "string", validation: { required: true } },
//msrepl_tran_version: { type: "string", validation: { required: true } }
}
}
},
error: function (e) {
//var response = JSON.parse(e.responseText);
var response = e.status;
console.log(response);
}
});
$("#grid").kendoGrid({
dataSource: dataSource,
pageable: true,
height: 400,
toolbar: ["create"],
columns: [
{ field: "ContentTypeId", editable: false, width: 90, title: "ID" },
{ field: "Description", title: "Content Type" },
{ command: ["edit", "destroy"] }
],
editable: "inline"
});
}]);
You mention best practices, I think it's better to leave your data source and manipulation in a factory or service like the data factories you had. Here's some specific references:
http://trochette.github.io/Angular-Design-Patterns-Best-Practices/#/keep_controllers_simple
http://trochette.github.io/Angular-Design-Patterns-Best-Practices/#/business_logic
this is a two part question from a JS newbie.
So, I was trying to create a backbone application using requireJS by following Thomas Davis's tutorial.
How do I go create Backbone collections out of an ajax call to a server that provides data in XML? collections.fetch() seem to be expecting a JSON backend.
while trying some things, I ended up with the following code, in which the page doesn't refresh upon populating the collection "bookStore" from within Ajax success-callback.
Here is how far I have gotten so far.
var bookListView = Backbone.View.extend({
el: $("#books"),
initialize: function () {
thisView = this;
$.ajax({
type: "GET",
url: "books.xml",
dataType: "xml",
success: function (data) {
console.log(data);
$(data).find('book').each(function (index) {
var bookTitle = $(this).find('name').text();
bookStore.add({ title: bookTitle });
console.log(seid);
});
thisView.collection = bookStore;
thisView.collection.bind('add', thisView.tryBind);
}
}).done(function (msg) {
alert("Data retrieved: " + msg);
});
this.collection = bookStore;
this.collection.bind("add", this.exampleBind);
this.collection.bind("refresh", function () { thisView.render(); });
/*
// This one works!
bookStore.add({ name: "book1" });
bookStore.add({ name: "book2" });
bookStore.add({ name: "book3" });
*/
},
tryBind: function (model) {
console.log(model);
},
render: function () {
var data = {
books: this.collection.models,
};
var compiledTemplate = _.template(bookListTemplate, data);
$("#books").html(compiledTemplate);
}
});
Here, the success call-back in the "initialize" function seems to be processing the data properly and adding to the collection. However, the page doesn't refreshed.
While I was stepping through the Firebug console, the page gets refreshed however. How do I solve this problem?
You can override the default parse function to provide XML support. It should return the data transformed into JSON http://backbonejs.org/#Collection-parse
Bind the render to a reset event instead of refresh for Backbone<1.0 or to a sync event for Backbone>=1.0
It could look like this
var Book = Backbone.Model.extend();
var Books = Backbone.Collection.extend({
model: Book,
url: "books.xml",
parse: function (data) {
var $xml = $(data);
return $xml.find('book').map(function () {
var bookTitle = $(this).find('name').text();
return {title: bookTitle};
}).get();
},
fetch: function (options) {
options = options || {};
options.dataType = "xml";
return Backbone.Collection.prototype.fetch.call(this, options);
}
});
var bookListView = Backbone.View.extend({
initialize: function () {
this.listenTo(this.collection, "sync", this.render);
},
render: function () {
console.log(this.collection.toJSON());
}
});
var bks = new Books();
new bookListView({collection: bks});
bks.fetch();
And a demo http://jsfiddle.net/ULK7q/73/