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
Related
qx.Class.define("webApp.backendjs.tables.RegionesModel", {
extend: qx.ui.table.model.Remote,
members: {
_loadRowCount: function () {
var params = {};
params.action = "getCount";
var rpc = new qx.io.remote.Rpc("http://qx.alpali.cl/svc/svc.php");
rpc.setProtocol("2.0");
rpc.setCrossDomain(true);
rpc.callAsync(qx.lang.Function.bind(this._onRowCountCompleted, this), "regiones.regiones.getNominaRegiones", params);
},
_onRowCountCompleted: function (result, exc) {
if (result !== null) {
this._onRowCountLoaded(result.count);
}
},
_loadRowData: function (firstRow, lastRow) {
var params = {};
params.action = "getData";
var rpc = new qx.io.remote.Rpc("http://qx.alpali.cl/svc/svc.php");
rpc.setProtocol("2.0");
rpc.setCrossDomain(true);
rpc.callAsync(qx.lang.Function.bind(this._onLoadRowDataCompleted, this), "regiones.regiones.getNominaRegiones", params);
},
_onLoadRowDataCompleted: function (result, exc) {
if (result !== null) {
this._onRowDataLoaded(result);
}
}
}
});
var RTRegionesModel = new webApp.backendjs.tables.RegionesModel();
RTRegionesModel.setColumns(["ID", "Cè´¸digo", "Nombre"], ["id", "region_id", "region_nombre"]);
var TableRegiones = new qx.ui.table.Table(RTRegionesModel);
TableRegiones.setTableModel(RTRegionesModel);
// THIS don't work, return 0
TableRegiones.addListener('appear', function () {
console.log("RTRegionesModel.getRowCount(): %s", RTRegionesModel.getRowCount());
}, RTRegionesModel);
// THIS don't work, return 0
TableRegiones.addListener('appear', function () {
console.log("RTRegionesModel.getRowCount(): %s", RTRegionesModel.getRowCount());
}, this);
this.getRoot().add(TableRegiones);
var button1 = new qx.ui.form.Button("How many record...", "icon/22/apps/internet-web-browser.png");
this.getRoot().add(button1,{right:50,top:50});
// this is ok, return teh value
button1.addListener("execute", function(e) {
console.log("RTRegionesModel.getRowCount(): %s", RTRegionesModel.getRowCount());
});
url for testing playground
i need the valor when remote table is loaded
what is the problem..???
thank.
PD: sorry for my bad and ugly english, my native language is spanish (chile), my best friend in this moment is googol
At the time that you are looking for the row count with your "THIS don't work" comment, the row count is not yet available because the network operation to retrieve the row count from the server has not yet been issued.
You probably want to be listening for the model's dataChanged event which is fired when a row count is loaded, or when the model data changes, such as this:
TableRegiones.getTableModel().addListener(
'dataChanged',
function ()
{
console.log(
"dataChanged: RTRegionesModel.getRowCount(): %s",
RTRegionesModel.getRowCount());
},
RTRegionesModel);
I'm trying to set a dictionary of changes in order to track any modifcation to the scope when I execute the take snapshot function, however, for some reason, it starts working at the third try, other cases, my current value is sync with the previous, any idea why?
My saving factory looks like :
.factory('AuthoringState', function() {
var track = 0;
var _pool = {};
return {
addChange : function(data) {
track++;
var temp = _.cloneDeep(data);
_pool[track] = temp;
temp['track'] = track;
return _pool[track];
},
undo : function(checklist) {
if(checklist['track'] === 1) {
return checklist;
}
return _pool[checklist['track'] - 1];
},
back : function(checklist) {
if(checklist['track'] === track) {
return checklist;
}
return _pool[checklist['track'] + 1];
}
}
})
and my controller like this:
.controller('Sample', function($scope, AuthoringState) {
var tasks = {
tasks:[
{value: 1, text: 'I\'m a task'},
{value: 2, text: 'I\'m another task'}]
};
var vm = this;
/**Init**/
vm.checklist = new CheckList(tasks);
vm.checklist = AuthoringState.addChange(vm.checklist);
/** Methods**/
$scope.snapshoot = function() {
vm.checklist = AuthoringState.addChange(vm.checklist);
}
$scope.undo = function() {
vm.checklist = AuthoringState.undo(vm.checklist);
}
$scope.back = function() {
vm.checklist = AuthoringState.back(vm.checklist);
}
$scope.check = function() {
console.log($scope.checklist);
}
});
Here is a jsbin to see it working.
By returning the snapshot objects themselves from your service and binding them to the template, you are allowing the user's changes to modify the snapshots. It sounds like you want them to be immutable instead, so a snapshot doesn't change after it's saved.
Instead, clone the snapshots when you restore them and return the clones, leaving the snapshots untouchable:
undo : function(checklist) {
if(checklist.track === 1) {
return checklist;
}
return _.cloneDeep(_pool[checklist.track - 1]);
},
back : function(checklist) {
if(checklist.track === track) {
return checklist;
}
return _.cloneDeep(_pool[checklist.track + 1]);
}
Also, when saving a new snapshot, don't replace the current checklist object on the scope with the snapshot from the pool (which makes the snapshot itself editable), all you need to do is update its track property:
addChange : function(data) {
track++;
var temp = _.cloneDeep(data);
_pool[track] = temp;
temp.track = track;
data.track = track;
},
And in the controller:
vm.checklist = new CheckList(tasks);
AuthoringState.addChange(vm.checklist);
$scope.snapshoot = function() {
AuthoringState.addChange(vm.checklist);
}
Combining these changes, we get this working version: http://jsbin.com/juwimepofi/1/edit?js,output
I have create a filter but this filter is not working with array inside array.
'http://plnkr.co/edit/oygy79j3xyoGJmiPHm4g?p=info'
Above plkr link is working demo.
app.filter('checkboxFilter', function($parse) {
var cache = { //create an cache in the closure
result: [],
checkboxData: {}
};
function prepareGroups(checkboxData) {
var groupedSelections = {};
Object.keys(checkboxData).forEach(function(prop) {
//console.log(prop);
if (!checkboxData[prop]) {
return;
} //no need to create a function
var ar = prop.split('=');
//console.log("ar is - "+ar);
if (ar[1] === 'true') {
ar[1] = true;
} //catch booleans
if (ar[1] === 'false') {
ar[1] = false;
} //catch booleans
/* replacing 0 with true for show all offers */
if(ar[0]=='SplOfferAvailable.text'){
ar[1]='true';
}else{
}
//make sure the selection is there!
groupedSelections[ar[0]] = groupedSelections[ar[0]] || [];
//at the value to the group.
groupedSelections[ar[0]].push(ar[1]);
});
return groupedSelections;
}
function prepareChecks(checkboxData) {
var groupedSelections = prepareGroups(checkboxData);
var checks = [];
//console.log(groupedSelections);
Object.keys(groupedSelections).forEach(function(group) {
//console.log("groupedSelections- "+groupedSelections);
//console.log("group- "+group);
var needToInclude = function(item) {
//console.log("item- "+item);
// use the angular parser to get the data for the comparson out.
var itemValue = $parse(group)(item);
var valueArr = groupedSelections[group];
//console.log("valueArr- "+valueArr);
function checkValue(value) { //helper function
return value == itemValue;
}
//check if one of the values is included.
return valueArr.some(checkValue);
};
checks.push(needToInclude); //store the function for later use
});
return checks;
}
return function(input, checkboxData, purgeCache) {
if (!purgeCache) { //can I return a previous 'run'?
// is the request the same as before, and is there an result already?
if (angular.equals(checkboxData, cache.checkboxData) && cache.result.length) {
return cache.result; //Done!
}
}
cache.checkboxData = angular.copy(checkboxData);
var result = []; // this holds the results
//prepare the checking functions just once.
var checks = prepareChecks(checkboxData);
input.every(function(item) {
if (checks.every(function(check) {
return check(item);
})) {
result.push(item);
}
return result.length < 10000000; //max out at 100 results!
});
cache.result = result; //store in chache
return result;
};
});
above code is for check box filter.
when i click on checkbox called "Availability" it does not filter the result.
Please help me out.
Thanks.
I think that the way you are navigating through json is wrong because if you put in this way it works
"Location": "Riyadh",
"AvlStatus": "AVAILABLE"
"Rooms": {.....
You have to go in some way through Rooms and right now I think you're not doing that
I'm trying to learn firebase/angularjs by extending an app to use firebase as the backend.
My forge looks like this
.
In my program I have binded firebaseio.com/projects to $scope.projects.
How do I access the children?
Why doesn't $scope.projects.getIndex() return the keys to the children?
I know the items are in $scope.projects because I can see them if I do console.log($scope.projects)
app.js
angular.module('todo', ['ionic', 'firebase'])
/**
* The Projects factory handles saving and loading projects
* from localStorage, and also lets us save and load the
* last active project index.
*/
.factory('Projects', function() {
return {
all: function () {
var projectString = window.localStorage['projects'];
if(projectString) {
return angular.fromJson(projectString);
}
return [];
},
// just saves all the projects everytime
save: function(projects) {
window.localStorage['projects'] = angular.toJson(projects);
},
newProject: function(projectTitle) {
// Add a new project
return {
title: projectTitle,
tasks: []
};
},
getLastActiveIndex: function () {
return parseInt(window.localStorage['lastActiveProject']) || 0;
},
setLastActiveIndex: function (index) {
window.localStorage['lastActiveProject'] = index;
}
}
})
.controller('TodoCtrl', function($scope, $timeout, $ionicModal, Projects, $firebase) {
// Load or initialize projects
//$scope.projects = Projects.all();
var projectsUrl = "https://ionic-guide-harry.firebaseio.com/projects";
var projectRef = new Firebase(projectsUrl);
$scope.projects = $firebase(projectRef);
$scope.projects.$on("loaded", function() {
var keys = $scope.projects.$getIndex();
console.log($scope.projects.$child('-JGTmBu4aeToOSGmgCo1'));
// Grab the last active, or the first project
$scope.activeProject = $scope.projects.$child("" + keys[0]);
});
// A utility function for creating a new project
// with the given projectTitle
var createProject = function(projectTitle) {
var newProject = Projects.newProject(projectTitle);
$scope.projects.$add(newProject);
Projects.save($scope.projects);
$scope.selectProject(newProject, $scope.projects.length-1);
};
// Called to create a new project
$scope.newProject = function() {
var projectTitle = prompt('Project name');
if(projectTitle) {
createProject(projectTitle);
}
};
// Called to select the given project
$scope.selectProject = function(project, index) {
$scope.activeProject = project;
Projects.setLastActiveIndex(index);
$scope.sideMenuController.close();
};
// Create our modal
$ionicModal.fromTemplateUrl('new-task.html', function(modal) {
$scope.taskModal = modal;
}, {
scope: $scope
});
$scope.createTask = function(task) {
if(!$scope.activeProject || !task) {
return;
}
console.log($scope.activeProject.task);
$scope.activeProject.task.$add({
title: task.title
});
$scope.taskModal.hide();
// Inefficient, but save all the projects
Projects.save($scope.projects);
task.title = "";
};
$scope.newTask = function() {
$scope.taskModal.show();
};
$scope.closeNewTask = function() {
$scope.taskModal.hide();
};
$scope.toggleProjects = function() {
$scope.sideMenuController.toggleLeft();
};
// Try to create the first project, make sure to defer
// this by using $timeout so everything is initialized
// properly
$timeout(function() {
if($scope.projects.length == 0) {
while(true) {
var projectTitle = prompt('Your first project title:');
if(projectTitle) {
createProject(projectTitle);
break;
}
}
}
});
});
I'm interested in the objects at the bottom
console.log($scope.projects)
Update
After digging around it seems I may be accessing the data incorrectly. https://www.firebase.com/docs/reading-data.html
Here's my new approach
// Load or initialize projects
//$scope.projects = Projects.all();
var projectsUrl = "https://ionic-guide-harry.firebaseio.com/projects";
var projectRef = new Firebase(projectsUrl);
projectRef.on('value', function(snapshot) {
if(snapshot.val() === null) {
console.log('location does not exist');
} else {
console.log(snapshot.val()['-JGTdgGAfq7dqBpSk2ls']);
}
});
$scope.projects = $firebase(projectRef);
$scope.projects.$on("loaded", function() {
// Grab the last active, or the first project
$scope.activeProject = $scope.projects.$child("a");
});
I'm still not sure how to traverse the keys programmatically but I feel I'm getting close
It's an object containing more objects, loop it with for in:
for (var key in $scope.projects) {
if ($scope.projects.hasOwnProperty(key)) {
console.log("The key is: " + key);
console.log("The value is: " + $scope.projects[key]);
}
}
ok so val() returns an object. In order to traverse all the children of projects I do
// Load or initialize projects
//$scope.projects = Projects.all();
var projectsUrl = "https://ionic-guide-harry.firebaseio.com/projects";
var projectRef = new Firebase(projectsUrl);
projectRef.on('value', function(snapshot) {
if(snapshot.val() === null) {
console.log('location does not exist');
} else {
var keys = Object.keys(snapshot.val());
console.log(snapshot.val()[keys[0]]);
}
});
$scope.projects = $firebase(projectRef);
$scope.projects.$on("loaded", function() {
// Grab the last active, or the first project
$scope.activeProject = $scope.projects.$child("a");
});
Note the var keys = Object.keys() gets all the keys at firebaseio.com/projects then you can get the first child by doing snapshot.val()[keys[0])
I'm finding it difficult to explain in words, so here's a snippet of code I'm trying out but Firefox/firebug goes into tailspin!
I'm trying to follow this and this as a guide. What I'm trying to do here is
new MyObject.Method('string',optionsArray);
optionsArray items are iterated and saved using the prototype function Set()
if(typeof(MyObj) == 'undefined') MyObj= {};
MyObj.Method = function initialise(id,options)
{
this.id = id;
this.options = options;
this.properties ={};
for (var i = 0; i < this.options.length; i++) // =>options.length=2 (correct)
{
var obj = this.options[i];
//get the keynames, pass with values to Set() to update properties
for (var keys in obj)
{
console.log(keys); //=> correctly prints 'property1' and 'currentValue'
this.Set(keys,obj); //=> this is i guess where it enters a loop?
}
}
}
//sets properties
MyObj.Method.prototype.Set = function (name, value)
{
this.properties[name.toLowerCase()] = value;
}
and in my html page script block, i have
window.onload = function () {
var options = [
{ property1: {
show: true,
min: 0,
max: 100
}
},
{
currentValue: {
show: true,
colour: 'black'
}
}
];
var myObj = new MyObj.Method('someDivId',options);
}
please advise if I'm over complicating the code. I think checking for hasOwnProperty would help.
This should be a cleaner way of achieving what you want:
function MyObj(id, options) { // a function that will get used as the constructor
this.id = id;
this.options = options;
this.properties = {};
this.set(options); // call the set method from the prototype
}
MyObj.prototype.set = function(options) { // set the options here
for(var i = 0, l = options.length; i < l; i++) {
var obj = this.options[i];
for(var key in obj) {
if (obj.hasOwnProperty(key)) { // this will exclude stuff that's on the prototype chain!
this.properties[key] = obj[key];
}
}
}
return this; // return the object for chaining purposes
// so one can do FooObj.set([...]).set([...]);
};
var test = new MyObj('simeDivId', [...]); // create a new instance of MyObj
test.set('bla', [...]); // set some additional options
Note: For what hasOwnProperty is about please see here.
I made a declaration for MyObj and removed the function name initialise since you're obviously declaring this function to be a property of MyObj. Your final code will then be like below, and that runs for me just fine. Please note that you cannot actually call the function until after you declare the prototype function because else the object will have no notion of the Set function.
var MyObj = {};
MyObj.Method = function (id,options)
{
this.id = id;
this.properties ={};
for (var i = 0; i < options.length; i++) // =>options.length=2 (correct)
{
var obj = options[i];
//get the keynames, pass with values to Set() to update properties
for (var keys in obj)
{
console.log(keys); //=> correctly prints 'property1' and 'currentValue'
this.Set(keys,obj); //=> this is i guess where it enters a loop?
}
}
}
MyObj.Method.prototype.Set = function (name, value)
{
this.properties[name.toLowerCase()] = value;
}
var options = [
{ property1: {
show: true,
min: 0,
max: 100
}
},
{
currentValue: {
show: true,
colour: 'black'
}
}
];
var myObj = new MyObj.Method('someDivId',options);
var MyObj = {};
MyObj.Method = function initialise(id,options) {
this.id = id;
this.options = options;
this.properties = {};
for (var i = 0; i < this.options.length; i++)
{
var obj = this.options[i];
for (var keys in obj) {
this.Set(keys,obj[keys]);
//*fix obj => obj[keys]
// (and it should be singular key rather then keys
}
}
console.log(this.properties) // will output what you want
}
//sets properties
MyObj.Method.prototype.Set = function (name, value) {
this.properties[name.toLowerCase()] = value;
}
var options = [{
property1: {
show: true,
min: 0,
max: 100
}
},{
currentValue: {
show: true,
colour: 'black'
}
}];
var myObj = new MyObj.Method('someDivId',options);
this should work problem is you had your myObj = new MyObj... outside your onload event and options was out of its scope as it was declared as private variable to the anonymous function bound to the onload event.
I've fixed also the way you was copying the values to the properties as it doubled the names of the property and made it a bit messy.