backbone - cannot loop through a collection - javascript

I am having trouble looping through a collection that helps populate a view with data. When I try to loop through the collection and get the following error,
Uncaught TypeError: Object # has no method 'each'
I have absolutley no idea why I am getting this error, other than the object obviosly doesn't have that method, however I only get this error when I run the drop function see code below.
Here is by backbone code,
GroupListView.prototype.keyup = function() {
this.filtered = this.collection.searchName(this.$el.find("#search-query").val());
return this.renderList(this.filtered);
};
GroupListView.prototype.renderList = function(collection) {
var $collection, $this;
console.log(this.$el);
console.log(collection);
if (!collection) {
collection = this.collection;
console.log(collection);
}
this.$el.find(".list-items").empty();
$this = this.$el.find("#people-groups-list");
$collection = collection;
if ($collection.length < 1) {
this.$el.find("#people-groups-list").hide();
return this.$el.find("#people-groups-list").after('<div class="activity-no-show">\
<p>To add a new group, click the + in the top right hand corner to get started.</p>\
</div>');
} else {
this.$el.find("#people-groups-list").show();
collection.each(function(item) {
//console.log(item);
var displayView;
displayView = new app.GroupListDisplayView({
model: item,
collection: $collection
});
return $this.append(displayView.render());
});
return this;
}
};
GroupListDisplayView.prototype.render = function() {
var $body;
//console.log(this.model.toJSON());
this.$el.html(this.template({
m: this.model.toJSON()
}));
$body = this.$el.find(".card-body");
$.each(this.model.get("people"), function(i, person) {
var personTile;
this.person = new app.Person({
id: person.id,
avatar: person.avatar,
first_name: person.first_name,
last_name: person.last_name
});
console.log(this.person);
personTile = new app.PersonTileView({
model: this.person
});
return $body.html(personTile.render());
});
return this.$el.attr("id", "group-card-" + this.model.id);
};
GroupListDisplayView.prototype.drop = function(e) {
var $collection, $model, person_id, request;
e.preventDefault();
$collection = this.collection;
person_id = e.originalEvent.dataTransfer.getData('Text');
request = new app.PersonGroupAdd;
$model = this.model;
return request.save({
async: true,
wait: true,
person_id: person_id,
group_id: this.model.get("id")
}, {
success: function(d) {
return $model.fetch({
async: true,
wait: true
});
}
});
};
GroupListView.prototype.initialize = function() {
this.collection.on("change", this.renderList, this);
this.collection.on("reset", this.render, this);
return this.renderList();
};

Try this instead, place this function with in your collection
parse: function (data) {
data.forEach(function (item) {
displayView = new app.GroupListDisplayView({
model: item,
collection: $collection
});
});
}
Hope this helps.

I'm not sure what the drop function has to do with it, but I see renderList being passed the results of searchName during keyup. What's likely happening is that searchName is returning a regular array, instead of a wrapped object that would have the each method (i.e. a Backbone.Collection, jQuery collection or Underscore wrapped object).
Instead of calling collection.each, use jQuery or Underscore:
$.each(collection, function(i, item) { ... });
or
_.each(collection, function(item, i, list) { ... });

Related

Access object context from prototype functions JavaScript

I have problems with object scope.
Here is my class code
// Table list module
function DynamicItemList(data, settings, fields) {
if (!(this instanceof DynamicItemList)) {
return new DynamicItemList(data, settings, fields);
}
this.data = data;
this.settings = settings;
this.fields = fields;
this.dataSet = {
"Result": "OK",
"Records": this.data ? JSON.parse(this.data) : []
};
this.items = this.dataSet["Records"];
this.generateId = makeIdCounter(findMaxInArray(this.dataSet["Records"], "id") + 1);
this.dataHiddenInput = $(this.settings["hidden-input"]);
}
DynamicItemList.RESULT_OK = {"Result": "OK"};
DynamicItemList.RESULT_ERROR = {"Result": "Error", "Message": "Error occurred"};
DynamicItemList.prototype = (function () {
var _self = this;
var fetchItemsList = function (postData, jtParams) {
return _self.dataSet;
};
var createItem = function (item) {
item = parseQueryString(item);
item.id = this.generateId();
_self.items.push(item);
return {
"Result": "OK",
"Record": item
}
};
var removeItem = function (postData) {
_self.items = removeFromArrayByPropertyValue(_self.items, "id", postData.id);
_self.dataSet["Records"] = _self.items;
_self.generateId = makeIdCounter(findMaxInArray(_self.dataSet["Records"], "id") + 1);
return DynamicItemList.RESULT_OK;
};
return {
setupTable: function () {
$(_self.settings["table-container"]).jtable({
title: _self.settings['title'],
actions: {
listAction: fetchItemsList,
deleteAction: removeItem
},
fields: _self.fields
});
},
load: function () {
$(_self.settings['table-container']).jtable('load');
},
submit: function () {
_self.dataHiddenInput.val(JSON.stringify(_self.dataSet["Records"]));
}
};
})();
I have problems with accessing object fields.
I tried to use self to maintain calling scope. But because it is initialized firstly from global scope, I get Window object saved in _self.
Without _self just with this it also doesn't work . Because as I can guess my functions fetchItemsList are called from the jTable context and than this points to Window object, so I get error undefined.
I have tried different ways, but none of them work.
Please suggest how can I solve this problem.
Thx.
UPDATE
Here is version with all method being exposed as public.
// Table list module
function DynamicItemList(data, settings, fields) {
if (!(this instanceof DynamicItemList)) {
return new DynamicItemList(data, settings, fields);
}
this.data = data;
this.settings = settings;
this.fields = fields;
this.dataSet = {
"Result": "OK",
"Records": this.data ? JSON.parse(this.data) : []
};
this.items = this.dataSet["Records"];
this.generateId = makeIdCounter(findMaxInArray(this.dataSet["Records"], "id") + 1);
this.dataHiddenInput = $(this.settings["hidden-input"]);
}
DynamicItemList.RESULT_OK = {"Result": "OK"};
DynamicItemList.RESULT_ERROR = {"Result": "Error", "Message": "Error occurred"};
DynamicItemList.prototype.fetchItemsList = function (postData, jtParams) {
return this.dataSet;
};
DynamicItemList.prototype.createItem = function (item) {
item = parseQueryString(item);
item.id = this.generateId();
this.items.push(item);
return {
"Result": "OK",
"Record": item
}
};
DynamicItemList.prototype.setupTable = function () {
$(this.settings["table-container"]).jtable({
title: this.settings['title'],
actions: this,
fields: this.fields
});
};
DynamicItemList.prototype.load = function () {
$(this.settings['table-container']).jtable('load');
};
DynamicItemList.prototype.submit = function () {
this.dataHiddenInput.val(JSON.stringify(this.dataSet["Records"]));
};
DynamicItemList.prototype.removeItem = function (postData) {
this.items = removeFromArrayByPropertyValue(this.items, "id", postData.id);
this.dataSet["Records"] = this.items;
this.generateId = makeIdCounter(findMaxInArray(this.dataSet["Records"], "id") + 1);
return DynamicItemList.RESULT_OK;
};
DynamicItemList.prototype.updateItem = function (postData) {
postData = parseQueryString(postData);
var indexObjToUpdate = findIndexOfObjByPropertyValue(this.items, "id", postData.id);
if (indexObjToUpdate >= 0) {
this.items[indexObjToUpdate] = postData;
return DynamicItemList.RESULT_OK;
}
else {
return DynamicItemList.RESULT_ERROR;
}
};
Your assigning a function directly to the prototype. DynamicItemList.prototype= Normally it's the form DynamicItemList.prototype.somefunc=
Thanks everyone for help, I've just figured out where is the problem.
As for last version with methods exposed as public.
Problematic part is
$(this.settings["table-container"]).jtable({
title: this.settings['title'],
actions: {
listAction: this.fetchItemsList,
createAction: this.createItem,
updateAction: this.updateItem,
deleteAction: this.removeItem
},
fields: this.fields
});
};
Here new object is created which has no idea about variable of object where it is being created.
I've I changed my code to the following as you can see above.
$(this.settings["table-container"]).jtable({
title: this.settings['title'],
actions: this,
fields: this.fields
});
And now it works like a charm. If this method has drawbacks, please let me know.
My problem was initially in this part and keeping methods private doesn't make any sense because my object is used by another library.
Thx everyone.
You need to make your prototype methods use the this keyword (so that they dyynamically receive the instance they were called upon), but you need to bind the instance in the callbacks that you pass into jtable.
DynamicItemList.prototype.setupTable = function () {
var self = this;
function fetchItemsList(postData, jtParams) {
return self.dataSet;
}
function createItem(item) {
item = parseQueryString(item);
item.id = self.generateId();
self.items.push(item);
return {
"Result": "OK",
"Record": item
};
}
… // other callbacks
$(this.settings["table-container"]).jtable({
title: this.settings['title'],
actions: {
listAction: fetchItemsList,
createAction: createItem,
updateAction: updateItem,
deleteAction: removeItem
},
fields: this.fields
});
};

Implement search effectively in Backbone.js

I am trying to perform a search on my current collection and if the results aren't retrieved i am trying to query my search api
Collection:
var Backbone = require('backbone'),
_ = require('underscore'),
Urls = require('../../libs/urls'),
services = require('../../libs/services'),
skuListModel = require('../../models/sku/SkuListModel');
var SkuListCollection= Backbone.Collection.extend({
model: skuListModel,
sync: function (method, model, options) {
options = _.defaults({}, options, {
readUrl: Urls.sku.list
});
return services.sync.call(model, method, model, options);
}
});
View
searchData: function (e) {
var self = this;
var models = this.skuCollection.filter(function (item) {
return item.get("sku_code").indexOf(e.target.value) > -1
});
console.log(models);
if (models != null) {
self.skuCollection.set(models);
}
else {
self.skuCollection.fetch({
data: {
search_string: e.target.value
}
}).then(function (response) {
console.log(response);
//self.skuCollection.add(self.skuSearchCollection.toJSON(), { silent: true });
});
}
}
My question effectively is how do i modify my current collection to store the retrieved results and if my solution seems effective.
Move your filtering logic to the collection
Use promises to unify your response : an immediately resolved deferred if you find models, the xhr object if you have to fetch the data
Customize the behavior of fetch via the set options, e.g {remove: false} to keep the existing models
These points lead to a collection definition :
var SkuListCollection = Backbone.Collection.extend({
skus: function(code) {
var self = this;
var filtered = function() {
return self.filter(function (item) {
return item.get("sku_code").indexOf(code) !== -1;
});
};
var models = filtered();
if (models.length) {
// models found : define a promise and resolve it
var dfd = $.Deferred();
dfd.resolve(models);
return dfd.promise();
} else {
// models missing: fetch and add them
return this.fetch({
remove: false,
data: {
search_string: code
}
}).then(filtered);
}
}
});
Your view would then be rewired as :
searchData: function (e) {
this.skuCollection.skus(e.target.value).then(function(models) {
// do what you have to do with the filtered models
});
}
And a demo http://jsfiddle.net/nikoshr/84342xer/1/

Ember inFlight Error on delete

Im getting this error and am having trouble debugging it:
Uncaught Error: Attempted to handle event `deleteRecord` on <Todos.Todo:ember309:29> while in state root.loaded.updated.inFlight.
All transactions between ember and my JSON API seem to be working fine, but if alter the state of the todo, by either renaming it or checking it as complete, and then do a delete, I get the error above.
the changes they are persisting, and if I delete a todo without making any changes, that works as well.
Here is some code:
Route
Todos.TodosRoute = Ember.Route.extend({
model: function () {
return this.store.find('todo');
}
});
Controller
Todos.TodoController = Ember.ObjectController.extend({
actions: {
editTodo: function () {
this.set('isEditing', true);
},
acceptChanges: function () {
this.set('isEditing', false);
if (Ember.isEmpty(this.get('model.title'))) {
this.send('removeTodo');
} else {
this.get('model').save();
}
},
removeTodo: function () {
var todo = this.get('model');
todo.deleteRecord();
todo.save();
},
},
isEditing: false,
isCompleted: function(key, value){
var model = this.get('model');
if (value === undefined) {
// property being used as a getter
return model.get('isCompleted');
} else {
// property being used as a setter
model.set('isCompleted', value);
model.save();
return value;
}
}.property('model.isCompleted')
});
Adapter
Todos.ApplicationAdapter = DS.RESTAdapter.extend({
defaultSerializer: "Todos/todosREST",
serialize: function(record, options) {
var tmp = get(record, 'store').serializerFor(record.constructor.typeKey).serialize(record, options);
console.log('ser2:', tmp)
return tmp
},
createRecord: function(store, type, record) {
var data = {};
var serializer = store.serializerFor(type.typeKey);
serializer.serializeIntoHash(data, type, record, { includeId: false });
var tmp = this.ajax(this.buildURL(type.typeKey), "POST", { data: data });
// console.log('createRecord:', tmp, data, record);
return tmp;
},
updateRecord: function(store, type, record) {
var data = {};
var serializer = store.serializerFor(type.typeKey);
serializer.serializeIntoHash(data, type, record);
var id = get(record, 'id');
var tmp = this.ajax(this.buildURL(type.typeKey, id), "PUT", { data: data })
console.log(this.buildURL(type.typeKey, id), "PUT", { data: data })
return tmp;
},
namespace: 'api'
});
Serializer
Todos.TodosRESTSerializer = DS.RESTSerializer.extend({
typeForRoot: function(root) {
var camelized = Ember.String.camelize(root);
var tmp = Ember.String.singularize(camelized);
console.log('Camelized:'. tmp)
return tmp;
},
keyForAttribute: function(attr) {
return Ember.String.underscore(attr);
},
serialize: function(record, options) {
// client to server
var json = {};
record.eachAttribute(function(name) {
json[serverAttributeName(name)] = record.get(name);
})
record.eachRelationship(function(name, relationship) {
if (relationship.kind === 'hasMany') {
json[serverHasManyName(name)] = record.get(name).mapBy('id');
}
});
console.log('options: ', options)
if (options && options.includeId) {
json.id = record.get('id');
}
return json;
},
serializeIntoHash: function(hash, type, record, options) {
// hash should be an empty object, so just extend it
Ember.merge(hash, this.serialize(record, options));
},
extractSingle: function(store, type, payload, id, requestType) {
// server to client
console.log('in serializer')
return this._super(store, type, post_payload, id, requestType);
},
extractArray: function(store, type, payload, id, requestType) {
// server to client
console.log(payload, type)
var todos = payload.objects;
payload = {todos: todos}
return this._super(store, type, payload, id, requestType);
},
});
Anyone have any ideas?

Updating a view in backbone when a collection is updated

I have a web app that I am building, I have form input that allows you to enter a name, on entering this name, I want to update a list with that inputted name, my problem is however that if add one name and then another the previos name that is outputted to the view, is overwritten (but if I refresh the page I get the full list). Here is my code,
GroupModalHeaderView.prototype.render = function() {
this.$el.empty();
if (this.model.isNew()) {
this.$el.append(this.template({
m: this.model.toJSON()
}));
return this.edit();
} else {
this.$el.append(this.template({
m: this.model.toJSON()
}));
this.$el.find(".modal-header-menu").show();
return this.$el.find(".icon-button-close-modal").show();
}
};
GroupModalHeaderView.prototype.save = function(e) {
var $collection, $this;
if (e) {
e.preventDefault();
}
$this = this;
if (this.$("#group-name").val() !== "") {
$collection = this.collection;
if (this.model.isNew()) {
this.collection.push(this.model);
}
return this.model.save({
name: this.$("#group-name").val(),
async: false,
wait: true
}, {
success: function() {
return $this.cancel();
}
});
}
};
GroupListView.prototype.events = {
"click .list-header-add": "add",
"click .list-header-expander": "showHide",
"keyup #search-query": "keyup"
};
GroupListView.prototype.initialize = function() {
//console.log("fired");
this.collection.on("change", this.renderList, this);
this.collection.on("reset", this.render, this);
return this.renderList();
};
GroupListView.prototype.renderList = function(collection) {
var responsiveHeight = $("body").height() - 400;
if($("#people-network-requests").is(":visible")) {
this.$el.find("#people-groups-list").height($("#people-people-list").height()-250+"px");
} else {
this.$el.find("#people-groups-list").height($("#people-people-list").height()+"px");
}
var $collection, $this;
if (!collection) {
collection = this.collection;
}
this.$el.find(".list-items").empty();
$this = this.$el.find("#people-groups-list");
this.$el.find(".list-items").removeClass("list-items-loading").empty();
$collection = collection;
if ($collection.length < 1) {
/*this.$el.find("#people-groups-inner").hide();
$(".activity-no-show").remove();
return this.$el.find("#people-groups-inner").append('<div class="activity-no-show">\
<p>To add a new group, click the + in the top right hand corner to get started.</p>\
</div>');*/
} else {
this.collection.each(function(item) {
var displayView;
displayView = new app.GroupListDisplayView({
model: item,
collection: $collection
});
console.log($this);
return $this.append(displayView.render());
});
return this;
}
};
return GroupListView;
})(app.BaseView);
GroupListDisplayView.prototype.render = function() {
//console.log(this.$el);
//alert("1");
var $body;
this.$el.html(this.template({
m: this.model.toJSON()
}));
$body = this.$el.find(".card-body");
$text = $body.text();
$.each(this.model.get("people"), function(i, person) {
var personTile;
this.person = new app.Person({
id: person.id,
avatar: person.avatar,
first_name: person.first_name,
last_name: person.last_name
});
personTile = new app.PersonTileView({
model: this.person
});
if(person.id) {
$body.append(personTile.render()).find(".instruction").remove();
}
});
return this.$el.attr("id", "group-card-" + this.model.id);
};
GroupListView.prototype.keyup = function() {
this.filtered = $collection.searchName(this.$el.find("#search-query").val());
//console.log(this.filtered);
return this.renderList(this.filtered);
};
this.collection.on("add", this.addDisplayView, this);
Then create a function addDisplayView that accepts the model for the view. You will need to refactor the this.collection.each(function(item)... part of your code to use the addDisplayView function.
GroupListView.prototype.addDisplayView = function(model){
var displayView = new app.GroupListDisplayView({
model: model,
collection: this.collection
});
// use this.$, as it is already mapped to the context of the view
return this.$("#people-groups-list").append(displayView.render());
}
You should also change this.collection.push(this.model); to this.collection.add(this.model);
addcollection.add(models, [options])
Add a model (or an array of models) to the collection, firing an "add" event. If a model property
is defined, you may also pass raw attributes objects, and have them be
vivified as instances of the model. Pass {at: index} to splice the
model into the collection at the specified index. If you're adding
models to the collection that are already in the collection, they'll
be ignored, unless you pass {merge: true}, in which case their
attributes will be merged into the corresponding models, firing any
appropriate "change" events.
http://documentcloud.github.io/backbone/#Collection-add

Backbone.model: Object function (a){return new n(a)} has no method 'has'

I did wrote the following code(*)
When I try to run the following code(**) in my js console,
I get the following result:
"your attributes are: ", Object // json object taken from the server as I was expecting
Object function (a){return new n(a)} has no method 'has'
Why do I get the issue about has no method 'has'?
-
(**)
require.config({
baseUrl: "/"
});
require(["js/models/task"], function ( Model ) {
var model = new Model({id: 1});
model.fetch();
console.log(model.attributes);
});
(*)
define([], function () {
var MyModel = Backbone.Model.extend({
initialize: function ()
{
this.bind("change", function () {
console.log("this model has been changed")
});
this.bind("error", function (model, error) {
console.log(error);
})
},
urlRoot: "/",
url: function () {
var base = this.urlRoot || (this.collection && this.collection.url) || "/";
if (this.isNew()) return base;
return base + this.id;
},
validate: function (attribute) {
if (typeof attribute === "object") {
console.log("your attributes are: ", attribute);
}
}
});
return MyModel;
});
fetch is asynchronous so try the following:
require(["js/models/task"], function ( Model ) {
var model = new Model({id: 1});
model.fetch({success: function() {
console.log(model.attributes);
}});
});

Categories

Resources