I have the following model and collection:
var UserModel = Backbone.Model.extend({
url: 'api/user',
idAttribute:'username',
defaults: {
username:'',
password:'',
email:'',
tags:''
}
});
var UserCollection= Backbone.Collection.extend({
url: 'api/user',
model: UserModel
});
When I retrieve a user from the collection using:
var myUser = collection.get(username);
the username has to be in the correct case, otherwise I just get null as a result.
Is there a way to tell backbone to ignore the case for certain operations like this on?
Sure, you just need to change the relevant code. It is on lines 240-242 of backbone.js (for documented 0.9.2 version):
get: function(attr) {
return this.attributes[attr];
},
Change it something like to:
get: function(attr) {
// will skip if null or undefined -- http://stackoverflow.com/questions/5113374/javascript-check-if-variable-exists-which-method-is-better
if (this.attributes[attr] != null) {
return this.attributes[attr];
}
// and then try to return for capitalized version -- http://stackoverflow.com/questions/1026069/capitalize-the-first-letter-of-string-in-javascript
else {
return this.attributes[attr.charAt(0).toUpperCase() + attr.slice(1)];
}
},
for collection changing
get: function(id) {
if (id == null) return void 0;
return this._byId[id.id != null ? id.id : id];
},
to something like this might work:
get: function(id) {
if (id == null) return void 0;
var firstCase = this._byId[id.id != null ? id.id : id];
if (firstCase != null) {
return firstCase;
}
else {
return this._byId[capitalize(id.id) != null ? capitalize(id.id) : capitalize(id)];
}
},
Related
My code has been working well until I introduced local storage. What I do is: when device is ready check if the values exist, if so get them. In the body of a function I assign a value to that variable scope and that's where I get this error.
$ionicPlatform.ready(function() {
if (localStorageService.get("modelTrue") == null) {
$scope.userModal.show();
localStorageService.set("modelTrue", "true")
}
else {
//Parameter to restore:
if(localStorageService.get("first") != null){
$scope.data.firstDisplay = JSON.parse(localStorageService.get("first"));
}
};
$scope.scanBarcode = function() {
$scope.data.firstDisplay = {'src' : 'img/1.png'}; //this is where I get the error
localStorageService.set("first", JSON.stringify($scope.data.firstDisplay));
}
When I call the last function I get the error: "TypeError: Cannot set property 'firstDisplay' of null".
I don't understand this behaviour. I am simply setting a variable to a value.
What might this mean?
The null comes from console.log($scope.data);
You need to declare $scope.data ={}; otherwise it is always null,
$ionicPlatform.ready(function() {
$scope.data ={};
if (localStorageService.get("modelTrue") == null) {
$scope.userModal.show();
localStorageService.set("modelTrue", "true")
} else {
//Parameter to restore:
if (localStorageService.get("first") != null) {
$scope.data.firstDisplay = JSON.parse(localStorageService.get("first"));
}
};
$scope.scanBarcode = function() {
$scope.data.firstDisplay = {
'src': 'img/1.png'
}; //this is where I get the error
localStorageService.set("first", JSON.stringify($scope.data.firstDisplay));
}
I am using the ag-grid for angular1, (and loving it), and I want my users to be able to reorgenize columns, change sortings, and everything, and that it will stay after a refresh.
It should not be very hard, except that the columns are circular (contains pointers to themselves), and thus I cannot parse them.
Code:
var columnDefsKey = "columnDefs["+$rootScope.page+"]";
var savedColumns = localStorage.getItem(columnDefsKey);
function saveColumnsState() {
var currentCol = vm.gridOptions.columnApi.getAllColumns();
if (!angular.equals(currentCol, savedColumns))
try {
localStorage.setItem(columnDefsKey, JSON.stringify(currentCol));
} catch (ex) {
log(ex);
log(currentCol);
}
}
And:
onColumnEverythingChanged: saveColumnsState,
onColumnVisible: saveColumnsState,
onColumnPinned: saveColumnsState,
onColumnResized: saveColumnsState,
onColumnRowGroupChanged: saveColumnsState,
onColumnValueChanged: saveColumnsState,
onColumnMoved: saveColumnsState,
onColumnGroupOpened: saveColumnsState,
It fails on the "try" every time:
TypeError: Converting circular structure to JSON(…) [Column, Column, Column, Column, Column, Column, Column, Column, Column, Column]
How can I do that? (save columns for later use)
If I manage to do that, I will be able to create several views without coding.
you can get the better understanding of the issue from below link
Chrome sendrequest error: TypeError: Converting circular structure to JSON
Also check below reference
https://github.com/isaacs/json-stringify-safe
The way to achieve this was to build my own column model, that I can save and parse again, and in which to save only necessary properties.
This method is XSS vulnerable, as I am evaluating functions, but it is a working solution.
columnsApi: {
key: null,
grid: null,
newColumnModel: {
headerName: "",
width: 200,
valueGetter: "",
filter: 'text',
aggFunc: 'none',
filterParams: {apply: true}
},
setKey: function (key) {
this.key = key;
},
setGrid: function (grid) {
this.grid = grid;
},
format: function (columns) {
var format = [];
angular.forEach(columns, function (col) {
var colDef = {
width: col.actualWidth,
pinned: col.pinned,
hide: !col.visible
};
format.push(angular.extend(col.colDef, colDef));
});
return format;
},
getIDs: function (columns) {
var ids = [];
angular.forEach(columns, function (col) {
ids.push(col.colId);
});
return ids;
},
stringify: function (columns) {
return JSON.stringify(columns, function (key, value) {
if (typeof value === "function")
return "/Function(" + value.toString() + ")/";
return value;
});
},
parse: function (string) {
return JSON.parse(string, function (key, value) {
if (typeof value === "string" &&
value.startsWith("/Function(") &&
value.endsWith(")/")) {
value = value.substring(10, value.length - 2);
return eval("(" + value + ")");
}
return value;
});
},
add: function (column) {
if (this.grid === null) {
console.error("Assertion error: grid must not be null");
return;
}
if(column.aggFunc == 'none')
column.aggFunc = undefined;
var groups = this.get().groups;
var newColumns = this.format(getGridColumns(this.grid));
newColumns.push(column);
this.grid.api.setColumnDefs(newColumns);
this.setGroups(groups);
},
save: function () {
var self = this;
if (this.key === null) {
console.error("Assertion error: key must not be null");
return;
}
if (this.grid === null) {
console.error("Assertion error: grid must not be null");
return;
}
var savedOptions = {
columns: self.format(getGridColumns(self.grid)),
groups: self.getIDs(self.grid.columnApi.getRowGroupColumns()),
sorting: self.grid.api.getSortModel(),
filter: self.grid.api.getFilterModel()
};
localStorage.setItem(this.key, this.stringify(savedOptions));
},
// Get function uses "eval" - XSS vulnerable.
get: function () {
if (this.key === null) {
console.error("Assertion error: key must not be null");
return;
}
var options = localStorage.getItem(this.key);
if (options)
options = this.parse(options);
return options;
},
remove: function (field) {
if (this.grid === null) {
console.error("Assertion error: grid must not be null");
return;
}
var newColumns = this.format(getGridColumns(this.grid));
angular.forEach(newColumns, function (col, key) {
if (col.field == field)
newColumns.splice(key, 1);
});
this.grid.api.setColumnDefs(newColumns);
},
setGroups: function (groups) {
var self = this;
angular.forEach(groups, function (id) {
angular.forEach(getGridColumns(self.grid), function (col) {
if (col.colId == id)
self.grid.columnApi.addRowGroupColumn(col);
});
});
}
}
This solution was written for Ag-Grid 5 I believe, and thus I am not sure if it still holds.
I'm having some problems with one async process on nodejs.
I'm getting some data from a remote JSON and adding it in my array, this JSON have some duplicated values, and I need check if it already exists on my array before add it to avoid data duplication.
My problem is when I start the loop between the JSON values, the loop call the next value before the latest one be process be finished, so, my array is filled with duplicated data instead of maintain only one item per type.
Look my current code:
BookRegistration.prototype.process_new_books_list = function(data, callback) {
var i = 0,
self = this;
_.each(data, function(book) {
i++;
console.log('\n\n ------------------------------------------------------------ \n\n');
console.log('BOOK: ' + book.volumeInfo.title);
self.process_author(book, function() { console.log('in author'); });
console.log('\n\n ------------------------------------------------------------');
if(i == data.length) callback();
})
}
BookRegistration.prototype.process_author = function(book, callback) {
if(book.volumeInfo.authors) {
var author = { name: book.volumeInfo.authors[0].toLowerCase() };
if(!this.in_array(this.authors, author)) {
this.authors.push(author);
callback();
}
}
}
BookRegistration.prototype.in_array = function(list, obj) {
for(i in list) { if(list[i] === obj) return true; }
return false;
}
The result is:
[{name: author1 }, {name: author2}, {name: author1}]
And I need:
[{name: author1 }, {name: author2}]
UPDATED:
The solution suggested by #Zub works fine with arrays, but not with sequelize and mysql database.
When I try to save my authors list on the database, the data is duplicated, because the system started to save another array element before finish to save the last one.
What is the correct pattern on this case?
My code using database is:
BookRegistration.prototype.process_author = function(book, callback) {
if(book.volumeInfo.authors) {
var author = { name: book.volumeInfo.authors[0].toLowerCase() };
var self = this;
models.Author.count({ where: { name: book.volumeInfo.authors[0].toLowerCase() }}).success(function(count) {
if(count < 1) {
models.Author.create(author).success(function(author) {
console.log('SALVANDO AUTHOR');
self.process_publisher({ book:book, author:author }, callback);
});
} else {
models.Author.find({where: { name: book.volumeInfo.authors[0].toLowerCase() }}).success(function(author) {
console.log('FIND AUTHOR');
self.process_publisher({ book:book, author:author }, callback);
});
}
});
// if(!this.in_array(this.authors, 'name', author)) {
// this.authors.push(author);
// console.log('AQUI NO AUTHOR');
// this.process_publisher(book, callback);
// }
}
}
How can I avoid data duplication in an async process?
This is because you are comparing different objects and result is always false.
Just for experiment type in the console:
var obj1 = {a:1};
var obj2 = {a:1};
obj1 == obj2; //false
When comparing objects (as well as arrays) it only results true when obj1 links to obj2:
var obj1 = {a:1};
var obj2 = obj1;
obj1 == obj2; //true
Since you create new author objects in each process_author call you always get false when comparing.
In your case the solution would be to compare name property for each book:
BookRegistration.prototype.in_array = function(list, obj) {
for(i in list) { if(list[i].name === obj.name) return true; }
return false;
}
EDIT (related to your comment question):
I would rewrite process_new_books_list method as follows:
BookRegistration.prototype.process_new_books_list = function(data, callback) {
var i = 0,
self = this;
(function nextBook() {
var book = data[i];
if (!book) {
callback();
return;
}
self.process_author(book, function() {
i++;
nextBook();
});
})();
}
In this case next process_author is being called not immediately (like with _.each), but after callback is executed, so you have consequence in your program.
Not sure is this works though.
Sorry for my English, I'm not a native English speaker
I am creating a contact Manager using backbone.js,this is my code
$(document).ready(function() {
var Contact=Backbone.Model.extend({
defaults: {
fname : '',
lname : '',
phoneno : ''
}
});
var ContactList=Backbone.Collection.extend({
model : Contact,
localStorage: new Store("ContactList-backbone")
});
var ContactView=Backbone.View.extend({
el : $('div#contactmanager'),
events: {
'click #additems' : 'add'
},
initialize: function() {
this.render();
this.collection = new ContactList();
},
add : function() {
s1=$('#fname').val();
s2=$('#lname').val();
s3=$('#phoneno').val();
if(s1 =="" || s2=="" || s3=="")
{
alert("Enter values in Textfield");
}
else
{
$('#tlist').append("<tr><td>"+s1+"</td><td>"+s2+"</td><td>"+s3+"</td> </tr>");
cont=new Contact({fname:s1,lname:s2,phoneno:s3});
this.collection.add(cont);
cont.save();
}
},
render : function() {
$(this.el).append("<label><b>First Name</b></label><input id= 'fname' type='text' placeholder='Write ur first name'></input>");
$(this.el).append("<br><label><b>Last Name</b></label><input id= 'lname' type='text' placeholder='Write ur last name'></input>");
$(this.el).append("<br><label><b>Phone Number</b></label><input id= 'phoneno' type='text' placeholder='Write ur phone number'></input>");
$(this.el).append("<br><button id='additems'>ADD</button>");
var showdata=localStorage.getItem('ContactList-backbone',this.model);
console.log(showdata,"showdata");
}
return this;
},
});
var contactManager=new ContactView();
});
This is how I used localstorage
function S4() {
return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
};
function guid() {
return (S4());
};
var Store = function(name)
{
this.name = name;
var store = localStorage.getItem(this.name);
this.data = (store && JSON.parse(store)) || {};
};
_.extend(Store.prototype,
{
save: function() {
localStorage.setItem(this.name, JSON.stringify(this.data));
},
create: function(model) {
if (!model.id) model.id = model.attributes.id = guid();
this.data[model.id] = model;
this.save();
return model;
},
Backbone.sync = function(method, model, options) {
var resp;
var store = model.localStorage || model.collection.localStorage;
switch (method) {
case "create": resp = store.create(model); break;
//I am using only create
}
if (resp) {
options.success(resp);
}
else {
options.error("Record not found");
}
};
The data is getting stored in local storage.
But I can't figure out how to show this data in my table when the page is reloded.
For eg: Iwant to show first name,lname and phone no in table ;
I am new to backbone so plz do help me
Basically you will want to bind the add event in your collection which gets will get called for each item that is being added to the collection and then in the function your binding it to add the code to add the rows to your table. Also you will want to remove the code that is in your current add method that adds the row since it will now be generated when the item gets added to your collection.
Using your code as a base something along the lines of
var ContactView=Backbone.View.extend({
el : $('div#contactmanager'),
events: {
'click #additems' : 'add'
},
initialize: function() {
this.render();
this.collection = new ContactList();
this.collection.bind('add', this.addContact, this);
},
addContact: function(contact) {
//this will get called when reading from local storage as well as when you just add a
//model to the collection
$('#table').append($('<tr><td>' + contect.get('name') + </td></tr>'));
}
Another point being that you have already have underscore.js on your page (since its a requirement for backbone.js) you may want to consider moving your code to generate html to a underscore.js template.
http://documentcloud.github.com/underscore/#template
since you're only using create, you're never going to hit read. Replace your switch statement with by adding a read method
switch (method)
{
case "read":
resp = model.id != undefined ? store.find(model) : store.findAll();
break;
case "create":
resp = store.create(model);
break;
}
I've seen a few different ways to get the next or previous model from a collection, but was wondering if anyone could offer some advice on the way I decided to implement it. My collection is ordered, but the id that i'm sorting on is not guaranteed to be sequential. It's only guaranteed to be unique. Assume that smaller ids are "older" entries to the collection and larger ids are "newer".
MyCollection = Backbone.Collection.extend({
model: MyModel,
initialize:function (){
this.getElement = this._getElement(0);
},
comparator: function(model) {
return model.get("id");
},
_getElement: function (index){
var self = this;
return function (what){
if (what === "next"){
if (index+1 >= self.length) return null;
return self.at(++index);
}
if (what === "prev"){
if (index-1 < 0 ) return null;
return self.at(--index);
}
// what doesn't equal anything useful
return null;
};
}
});
When using getElement, I do things like getElement("next") and getElement("prev") to ask for the next or previous model in my collection. What is returned from getElement is the actual model, not the index. I know about collection.indexOf, but I wanted a way to loop through a collection without first having a model to start from. Is this implementation harder than it needs to be?
I would do something like this. Keep in mind that there isn't any error handling currently so if you are currently at the first model in the collection and try to get the previous you will probably get an error.
MyCollection = Backbone.Collection.extend({
model: MyModel,
initialize:function (){
this.bindAll(this);
this.setElement(this.at(0));
},
comparator: function(model) {
return model.get("id");
},
getElement: function() {
return this.currentElement;
},
setElement: function(model) {
this.currentElement = model;
},
next: function (){
this.setElement(this.at(this.indexOf(this.getElement()) + 1));
return this;
},
prev: function() {
this.setElement(this.at(this.indexOf(this.getElement()) - 1));
return this;
}
});
To progress to the next model collection.next(). To progress to the next model and return it var m = collection.next().getElement();
To explain a little better how next/prev works.
// The current model
this.getElement();
// Index of the current model in the collection
this.indexOf(this.getElement())
// Get the model either one before or one after where the current model is in the collection
this.at(this.indexOf(this.getElement()) + 1)
// Set the new model as the current model
this.setElement(this.at(this.indexOf(this.getElement()) + 1));
I've done this slightly differently in that I'm adding the methods to the model rather than to the collection. That way, I can grab any model, and get the next one in the sequence.
next: function () {
if (this.collection) {
return this.collection.at(this.collection.indexOf(this) + 1);
}
},
prev: function () {
if (this.collection) {
return this.collection.at(this.collection.indexOf(this) - 1);
}
},
Bumping this old thread with a somewhat more generic solution:
Stuff to add to Collection.prototype
current: null,
initialize: function(){
this.setCurrent(0);
// whatever else you want to do here...
},
setCurrent: function(index){
// ensure the requested index exists
if ( index > -1 && index < this.size() )
this.current = this.at(index);
else
// handle error...
},
// unnecessary, but if you want sugar...
prev: function() {
this.setCurrent(this.at(this.current) -1);
},
next: function() {
this.setCurrent(this.at(this.current) +1);
}
you can then use the sugar methods to get the prev/next model like so...
collection.prev();
collection.next();
My Backbone SelectableCollection class:
Backbone.Collection.extend({
selectNext: function () {
if(this.cursor < this.length - 1) {
this.cursor++;
this.selected = this.at(this.cursor);
this.trigger('selected', this.selected);
}
},
selectPrevious: function () {
if(this.cursor > 0) {
this.cursor--;
this.selected = this.at(this.cursor);
this.trigger('selected', this.selected);
}
},
selectById: function (id) {
this.selected = this.get(id);
this.cursor = this.indexOf(this.selected);
this.trigger('selected', this.selected);
},
unselect: function () {
this.cursor = null;
this.selected = null;
this.trigger('selected', null);
}
});