I need to push a JSON object to AngularJS and need to check before if the value for one of the objects exist. I need to overwrite the data.
$scope.setData = function(survey, choice) {
keepAllData.push({
'surveyId': survey.id,
'choiceId': choice.id
});
console.log(keepAllData);
toArray(keepAllData);
alert(JSON.stringify(toArray(keepAllData)));
$scope.keepAllDatas.push({
'surveyId': survey.id,
'choiceId': choice.id
});
var items = ($filter('filter')(keepAllDatas, {
surveyId: survey.id
}));
}
function toArray(obj) {
var result = [];
for (var prop in obj) {
var value = obj[prop];
console.log(prop);
if (typeof value === 'object') {
result.push(toArray(value));
console.log(result);
} else {
result.push(value);
console.log(result);
}
}
return result;
}
If the survey id exists in keepalldata, I need to change the recent value with choiceid. Is it possible to do with AngularJS?
Try with this: Before pushing data you have to check if the survey id exists or not. If it exists you have to update choice with the corresponding survey id, otherwise you can push directly.
$scope.setData = function(survey, choice) {
var item = $filter('filter')(keepAllData, {
surveyId: survey.id
});
if (!item.length) {
keepAllData.push({
'surveyId': survey.id,
'choiceId': choice.id
});
} else {
item[0].choiceId = choice.id;
}
console.log(keepAllData);
}
Demo
$scope.keepAllDatas = [];
$scope.setData = function(survey, choice) {
if($scope.keepAllDatas.length == 0) {
$scope.keepAllDatas.push({'surveyId':survey.id,'choiceId':choice.id});
}
else {
var items = ($filter('filter')( $scope.keepAllDatas, {surveyId: survey.id }));
for (var i = items.length - 1; i >= 0; i--) {
// alert(items[i].surveyId);
if(items[i].surveyId == survey.id) {
console.log($scope.keepAllDatas.indexOf(survey.id));
$scope.keepAllDatas.splice($scope.keepAllDatas.indexOf(survey.id),1);
console.log("Removed data")
}
}
$scope.keepAllDatas.push({'surveyId':survey.id, 'choiceId':choice.id});
console.log( $scope.keepAllDatas)
// alert(items[0].surveyId);
}
}
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 using Bootstrap Typeahead along with Backbone to display data fetched from the server based on user input. The backing Backbone collection, which is used to store the fetched data, is created once on app startup. The collection is re-used for every new search in Typeahead.
The issue I am having is the browser memory usage keeps going up every time users searches for something. My question is really how can I make sure the old result/data in collection is garbage collected when the new one comes in? Also is re-using the same collection for new search the right way to go?
The collection js file
define([
"data",
"backbone",
"vent"
],
function (data, Backbone, vent) {
var SearchCollection = Backbone.Collection.extend({
model:Backbone.Model,
url:function () {
return data.getUrl("entitysearch");
},
initialize:function (models, options) {
var self=this;
this.requestAborted = false;
this.categories = (options && options.categories) || ['counterparty', 'company', 'user'];
this.onItemSelected = options.onItemSelected;
this.selectedId = options.selectedId; // should be prefixed with type eg. "company-12345"
_.bindAll(this, "entitySelected");
vent.bindTo(vent, "abortSearchAjax", function () {
this.requestAborted = true;
}, this);
},
search:function (criteria) {
var self = this,
results = [];
// abort any existing requests
if (this.searchRequest) {
this.searchRequest.abort();
}
self.requestAborted= false;
this.searchRequest = this.fetch({
data:$.param({
query:criteria,
types: this.mapEntityTypesToCodes(this.categories),
fields:'ANY',
max: 500
})
})
.done(function(response, textStatus, jqXHR) {
if (!self.requestAborted){
results = self.processResponse(response);
}
})
.fail(function(jqXHR, textStatus, errorThrown) {
if(errorThrown === "Unauthorized" || errorThrown === "Forbidden") {
alert("Either you do not have the right permissions to search for entities or you do not have a valid SSO token." +
" Reload the page to update your SSO token.");
}
})
.always(function(){
if (!self.requestAborted){
self.reset(results);
self.trigger('searchComplete');
}
});
},
/**
* Backbone parse won't work here as it requires you to modify the original response object not create a new one.
* #param data
* #return {Array}
*/
processResponse:function (response) {
var self = this,
result = [];
_.each(response, function (val, key, list) {
if (key !== 'query') {
_.map(val, function (v, k, l) {
var id;
v.type = self.mapEntityShortName(key);
id = v.id;
v.id = v.type + '-' + v.id;
v.displayId = id;
});
result = result.concat(val);
}
});
return result;
},
mapEntityTypesToCodes:function (types) {
var codes = [],
found = false;
_.each(types, function(el, index, list) {
{
switch (el) {
case 'counterparty':
codes.push('L5');
found = true;
break;
case 'company':
codes.push('L3');
found = true;
break;
case 'user':
codes.push('user');
found = true;
break;
}
}
});
if (!found) {
throw "mapEntityTypesToCodes - requires an array containing one or more types - counterparty, company, user";
}
return codes.join(',');
},
mapEntityShortName: function(name) {
switch (name) {
case 'parties':
return 'counterparty';
break;
case 'companies':
return 'company';
break;
case 'users':
return 'user';
break;
}
},
entitySelected:function (item) {
var model,
obj = JSON.parse(item),
data;
model = this.get(obj.id);
if (model) {
model.set('selected', true);
data = model.toJSON();
this.selectedId = obj.id;
//correct the id to remove the type eg. company-
data.id = data.displayId;
this.onItemSelected && this.onItemSelected(data);
} else {
throw "entitySelected - model not found";
}
},
openSelectedEntity: function() {
var model = this.get(this.selectedId);
if (model) {
vent.trigger('entityOpened', {
id: this.selectedId.split('-')[1],
name: model.get('name'),
type: model.get('type')
});
}
},
entityClosed:function (id) {
var model;
model = this.where({
id:id
});
if (model.length) {
model[0].set('selected', false);
}
}
});
return SearchCollection;
});
The View js file
define([
"backbone",
"hbs!modules/search/templates/search",
"bootstrap-typeahead"
],
function (Backbone, tpl) {
return Backbone.Marionette.ItemView.extend({
events: {
'click .action-open-entity': 'openEntity'
},
className: 'modSearch',
template:{
type:'handlebars',
template:tpl
},
initialize:function (options) {
_.bindAll(this, "render", "sorter", "renderSearchResults", "typeaheadSource");
this.listen();
this.categoryNames = options.categoryNames;
this.showCategoryNames = options.showCategoryNames;
this.autofocus = options.autofocus;
this.initValue = options.initValue;
this.disabled = options.disabled;
this.updateValueOnSelect = options.updateValueOnSelect;
this.showLink = options.showLink;
this.resultsLength = 1500;
},
listen:function () {
this.collection.on('searchComplete', this.renderSearchResults);
this.collection.on('change:selected', this.highlightedItemChange, this);
},
resultsFormatter:function () {
var searchResults = [],
that = this;
this.collection.each(function (result) {
searchResults.push(that._resultFormatter(result));
});
return searchResults;
},
_resultFormatter:function (model) {
var result = {
name:model.get('name'),
id:model.get('id'),
displayId: model.get('displayId'),
aliases:model.get('aliases'),
type:model.get('type'),
marketsPriority:model.get('marketsPriority')
};
if (model.get('ssoId')) {
result.ssoId = model.get('ssoId');
}
return JSON.stringify(result);
},
openEntity: function() {
this.collection.openSelectedEntity();
},
serializeData:function () {
return {
categoryNames:this.categoryNames,
showCategoryNames: this.showCategoryNames,
initValue:this.initValue,
autofocus:this.autofocus,
showLink:this.showLink
};
},
onRender:function () {
var self = this,
debouncedSearch;
if (this.disabled === true) {
this.$('input').attr('disabled', 'disabled');
} else {
debouncedSearch = _.debounce(this.typeaheadSource, 500);
this.typeahead = this.$('.typeahead')
.typeahead({
source: debouncedSearch,
categories:{
'counterparty':'Counterparties',
'company':'Companies',
'user':'Users'
},
minLength:3,
multiSelect:true,
items:this.resultsLength,
onItemSelected:self.collection.entitySelected,
renderItem:this.renderDropdownItem,
matcher: this.matcher,
sorter:this.sorter,
updateValueOnSelect:this.updateValueOnSelect
})
.data('typeahead');
$('.details').hide();
}
},
onClose: function(){
this.typeahead.$menu.remove();
},
highlightedItemChange:function (model) {
this.typeahead.changeItemHighlight(model.get('displayId'), model.get('selected'));
},
renderSearchResults:function () {
this.searchCallback(this.resultsFormatter());
},
typeaheadSource:function (query, searchCallback) {
this.searchCallback = searchCallback;
this.collection.search(query);
},
/**
* Called from typeahead plugin
* #param item
* #return {String}
*/
renderDropdownItem:function (item) {
var entity,
marketsPriority = '',
aliases = '';
if (!item) {
return item;
}
if (typeof item === 'string') {
entity = JSON.parse(item);
if (entity.marketsPriority && (entity.marketsPriority === "Y")) {
marketsPriority = '<span class="marketsPriority">M</span>';
}
if (entity.aliases && (entity.aliases.constructor === Array) && entity.aliases.length) {
aliases = ' (' + entity.aliases.join(', ') + ') ';
}
if (entity.type === "user"){
entity.displayId = entity.ssoId;
}
return [entity.name || '', aliases, ' (', entity.displayId || '', ')', marketsPriority].join('');
}
return item;
},
matcher: function(item){
return item;
},
/**
* Sort typeahead results - called from typeahead plugin
* #param items
* #param query
* #return {Array}
*/
sorter:function (items, query) {
var results = {},
reducedResults,
unmatched,
filteredItems,
types = ['counterparty', 'company', 'user'],
props = ['displayId', 'name', 'aliases', 'ssoId'],
type,
prop;
query = $.trim(query);
for (var i = 0, j = types.length; i < j; i++) {
type = types[i];
filteredItems = this._filterByType(items, type);
for (var k = 0, l = props.length; k < l; k++) {
prop = props[k];
unmatched = [];
if (!results[type]) {
results[type] = [];
}
results[type] = results[type].concat(this._filterByProperty(query, filteredItems, prop, unmatched));
filteredItems = unmatched;
}
}
reducedResults = this._reduceItems(results, types, this.resultsLength);
return reducedResults;
},
/**
* Sort helper - match query string against a specific property
* #param query
* #param item
* #param fieldToMatch
* #param resultArrays
* #return {Boolean}
* #private
*/
_matchProperty:function (query, item, fieldToMatch, resultArrays) {
if (fieldToMatch.toLowerCase().indexOf(query.toLowerCase()) === 0) {
resultArrays.beginsWith.push(item);
} else if (~fieldToMatch.indexOf(query)) resultArrays.caseSensitive.push(item)
else if (~fieldToMatch.toLowerCase().indexOf(query.toLowerCase())) resultArrays.caseInsensitive.push(item)
else if(this._fieldConatins(query, fieldToMatch, resultArrays)) resultArrays.caseInsensitive.push(item)
else return false;
return true;
},
_fieldConatins:function (query, fieldToMatch, resultArrays) {
var matched = false;
var queryList = query.split(" ");
_.each(queryList, function(queryItem) {
if(fieldToMatch.toLowerCase().indexOf(queryItem.toLowerCase()) !== -1) {
matched = true;
return;
}
});
return matched;
},
/**
* Sort helper - filter result set by property type (name, id)
* #param query
* #param items
* #param prop
* #param unmatchedArray
* #return {Array}
* #private
*/
_filterByProperty:function (query, items, prop, unmatchedArray) {
var resultArrays = {
beginsWith:[],
caseSensitive:[],
caseInsensitive:[],
contains:[]
},
itemObj,
item,
isMatched;
while (item = items.shift()) {
itemObj = JSON.parse(item);
isMatched = itemObj[prop] && this._matchProperty(query, item, itemObj[prop].toString(), resultArrays);
if (!isMatched && unmatchedArray) {
unmatchedArray.push(item);
}
}
return resultArrays.beginsWith.concat(resultArrays.caseSensitive, resultArrays.caseInsensitive, resultArrays.contains);
},
/**
* Sort helper - filter result set by entity type (counterparty, company, user)
* #param {Array} items
* #param {string} type
* #return {Array}
* #private
*/
_filterByType:function (items, type) {
var item,
itemObj,
filtered = [];
for (var i = 0, j = items.length; i < j; i++) {
item = items[i];
itemObj = JSON.parse(item);
if (itemObj.type === type) {
filtered.push(item);
}
}
return filtered;
},
/**
* Sort helper - reduce the result set down and split between the entity types (counterparty, company, user)
* #param results
* #param types
* #param targetLength
* #return {Array}
* #private
*/
_reduceItems:function (results, types, targetLength) {
var categoryLength,
type,
len,
diff,
reduced = [],
reducedEscaped = [];
categoryLength = Math.floor(targetLength / types.length);
for (var i = 0, j = types.length; i < j; i++) {
type = types[i];
len = results[type].length;
diff = categoryLength - len;
if (diff >= 0) { // actual length was shorter
reduced = reduced.concat(results[type].slice(0, len));
categoryLength = categoryLength + Math.floor(diff / (types.length - (i + 1)));
} else {
reduced = reduced.concat(results[type].slice(0, categoryLength));
}
}
_.each(reduced, function(item) {
item = item.replace(/\'/g,"`");
reducedEscaped.push(item);
});
return reducedEscaped;
}
});
});