find updated field in object - scope.$watch - angularjs - angularjs

If i do:
scope.$watch('obj', function(newObj, oldObj) {
//...
}, true);
How do I find the key-value pair in the object that changed?
.
Only to understand what I try to do:
I have an object of the form:
scope.actions = {
action1: false,
action2: false
}
When the boolean changes, I want to assign function calls to it. Something like, DO-action - UNDO-action.
So I watch it the following way:
scope.$watch('actions', function(newObj, oldObj) {
/*PSEUDO CODE START*/
IF (action1 changed && action1 true) {
do-func1();
}
IF (action1 changed && action1 false) {
undo-func1();
}
...
/*PSEUDO CODE END*/
}, true);
My problem here is, that if I check the values for their boolean, all the functions get called. So the point here is, how do I find the changed key-value pair in the object?

scope.$watch('actions', function(newActions, oldActions) {
for(var i in newActions) {
if(newActions.hasOwnProperty(i)) {
var newAction = newActions[i];
var oldAction = oldActions[i];
if(newAction !== oldAction) {
if(newAction === true) {
doActions[i](); // do-func-i();
} else if (newAction === false) {
undoActions[i](); // undo-func-i();
}
}
}
}
}, true);
doActions here is a conventional map of actions
doActions = {
action1 : function() {
console.log('action1');
},
action2 : function() {
console.log('action2');
}
}
doActions['action1'] will reference first function
You may have an array as well, but then you'll need to introduce an index to fetch proper function doActions[0]

Related

Cannot read property 'emit' of undefined when trying to emit a document

I am trying to create a design for tags of entities in PouchDB with ReactJS. I managed to save my design using the put function, but when I query my design, the response is just an empty array and I am getting following error in console:
TypeError: Cannot read property 'emit' of undefined
I think the problem is in my function that I later use as a map parameter to my design variable:
function emitTagsMap(doc)
{
if (doc !== undefined)
{
if (Array.isArray(doc.tags))
{
doc.tags.forEach(x =>
{
/* Here is probably the problem - this.db is undefined */
this.db.emit(x, null);
});
}
}
};
this.db is declared in constructor:
constructor(service, name)
{
if (!service || !name) throw new Error("PouchDatabase initialized incorrectly");
this.name = name;
this.db = new PouchDB(name);
this.service = service;
this.tagsView();
}
Please bare in mind that I am completely new to PouchDB.
Any ideas how can I initialize the emit function?
Thank you in advance.
I assume, that your function is a part of a JavaScript class (otherwise you have to explain the idea with this). In ES6, you have to bind this to your regular functions. You have two options:
First - bind it via constructor:
constructor() {
this.emitTagsMap = this.emitTagsMap.bind(this);
}
Second - declare the function as an arrow one. This way, react will bind it for you:
emitTagsMap = (doc) =>
{
if (doc !== undefined)
{
if (Array.isArray(doc.tags))
{
doc.tags.forEach(x =>
{
/* Here is probably the problem - this.db is undefined */
this.db.emit(x, null);
});
}
}
};
You don't need to call emit over the database object.
Try this:
function emitTagsMap(doc)
{
if (doc !== undefined)
{
if (Array.isArray(doc.tags))
{
doc.tags.forEach(x =>
{
emit(x, null);
});
}
}
};
According to the PouchDB docs a design document is formed like this:
// first create a new design doc and pass your map function as string into it
var ddoc = {
_id: "_design/my_index",
views: {
by_name: {
map: "function (doc) { if (doc !== undefined) { if (Array.isArray(doc.tags)) { doc.tags.forEach(x => { emit(x, null); }); } } }"
}
}
};
// save it
db.put(ddoc).then(function () {
// success!
}).catch(function (err) {
// some error (maybe a 409, because it already exists?)
});
//Then you actually query it, by using the name you gave the design document when you saved it:
db.query('my_index/by_name').then(function (res) {
// got the query results
}).catch(function (err) {
// some error
});
https://pouchdb.com/guides/queries.html

Remove $watch and bring the logic out of the $watch in AngularJS

I have a code in AngularJS which looks like below :
$scope.startWatching = function () {
return $scope.$watch('form', function (n, o) {
var timeoutPromise;
$timeout.cancel(timeoutPromise); //does nothing, if timeout alrdy done
timeoutPromise = $timeout(function () {
if (n !== o) {
if ($scope.isLegacy) {
$scope.showCompleteBtn = $scope.showCompleteButton2();
} else {
$scope.showCompleteBtn = $scope.showCompleteButton();
}
}
}, 400);
}, true);
So whenever form changes, either $scope.showCompleteButton2() is called or $scope.showCompleteButton() is called.
The problem is that the $watch() gets called many number if times, so I need to bring these two methods out of the $watch().
Watchers like event listeners should only be added once when the DOM is built. And removed when the DOM is torn down.
If the code needs to enable or disable the actions performed by the watcher, provide a state in the model to do so:
var enableWatch = false;
$scope.startWatching = function () {
enableWatch = true;
};
var timeoutPromise;
$scope.$watch('form', function (n, o) {
if (!enableWatch) return;
//ELSE
timeoutPromise && $timeout.cancel(timeoutPromise);
timeoutPromise = $timeout(function () {
if (n !== o) {
if ($scope.isLegacy) {
$scope.showCompleteBtn = $scope.showCompleteButton2();
} else {
$scope.showCompleteBtn = $scope.showCompleteButton();
}
}
}, 400);
}, true);
The watcher ignores changes when the enableWatch variable is false. Set the variable to true to enable the specified actions.

Multiple optional filters in angular

I am very new to angular and, I am not sure how to control the behavior of my filters.
In the app, I have two different single-select drop down controls that filter the results of my data set and fill a table. However, even though these filters work, the results are dependent of both controls and if both are not being used , the empty set is returned. So, my question is: How can I use these filters optionally? So, the app returns every result when the filters are not used or returns the filtered results by one of the controls or both?
Thank you
Here is the code:
AngularJS
The filters for each control. They look very similar:
.filter('byField', function () {
return function (results, options) {
var items = { options: options, out: [] };
angular.forEach(results, function (value, key) {
for (var i in this.options) {
if ((options[i].value === value.fieldId &&
options[i].name === "Field" &&
options[i].ticked === true)) {
this.out.push(value);
}
}
}, items);
return items.out;
};
})
.filter('byClass', function () {
return function (results, options) {
var items = { options: options, out: [] };
angular.forEach(results, function (value, key) {
for (var i in this.options) {
if ((options[i].value === value.documentClass &&
options[i].name === "Class" &&
options[i].ticked === true)) {
this.out.push(value);
}
}
}, items);
return items.out;
};
})
HTML
This is what I am doing to populate the rows of the table:
<tr ng-repeat="result in results | byField:outputFields | byClass:outputClasses">
<td>{{result.documentId}}</td>
...
</tr>
Dorado7.1 in all event listeners provides a view implicit variable pointing to the current event host's view, the variable can completely replace the use of this scenario.
Well, as I imagined the answer was more related to set theory than to angular.
I just made an union between the empty set and every result, and it worked.
.filter('byField', function () {
return function (results, options) {
var items = { options: options, out: [] };
angular.forEach(results, function (value, key) {
if (options.length) {
for (var i in this.options) {
if ((options[i].value === value.fieldId &&
options[i].name === "Field" &&
options[i].ticked === true)) {
this.out.push(value);
}
}
} else {
this.out = results.slice();
}
}, items);
return items.out;
};
})

Test multiple properties using _.every

I was asked to change some code using lodash's _.every:
//for every item in collection, check if "someProp" is true,
//but only if "someProp2" isn't "-1". If "someProp" is true for
//every item in collection, return true.
$scope.areAllTrue = function() {
for(var i=0; i<$scope.collection.length; i++){
if($scope.collection[i].someProp2 === -1) {
continue;
}
if(!$scope.collection[i].someProp) {
return false;
}
}
return true;
};
So following the lodash example of:
_.every(users, 'active', false);
We get:
$scope.areAllTrue = function() {
return _.every($scope.collection, 'someProp', true)
};
This handles the "For every item in the collection, check if someProp is true, if all are true, return true." But can I do the "continue" check here somehow?
Edit: Can I use two predicates with "_.every" somehow? Like if someProp1 === true || someProp2 === -1 ?
_.every() can use a predicate function:
_.every(users, function(user) {
return user.someProp2 === -1 || user.someProp;
});
You can also skip lodash, and use Array.prototype.every:
users.every(function(user) {
return user.someProp2 === -1 || user.someProp;
});

Nodejs async data duplication

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

Resources