angular.isUndefined() function working abruptly - javascript

angular.module("customFilters", [])
.filter("unique", function () {
return function (data, propertyName) {
if (angular.isArray(data) && angular.isString(propertyName)) {
var results = [];
var keys = {};
for (var i = 0; i < data.length; i++) {
var val = data[i][propertyName];
console.log(angular.isUndefined(keys[val]))
if (angular.isUndefined(keys[val])) {
keys[val] = true;
results.push(val);
}
}
return results;
} else {
return data;
}
}
});
i am new to angularjs and facing problem with angular.isUndefined() function.
So the issue is like i am adding properties to an object with the help of for loop and also checking that if a particular property is already defined for the object, if the property is defined then i am not adding that property to the object but when i am using angular.isUndefined() to check the property it returns false even when the property is there in the object and keeps doing so two times and for the third time it returns true when i call angular.isUndefined().
Please help in here.

Related

Why is a function returning undefined and how to debug it?

I'm experimenting with closures and classes in data variables and in the example below I'm getting undefined even though I placed a console.log() right before the function returns the result and it isn't undefined. It seems to work if it isn't attached to an event handler. Can someone tell me why is this happening and if there is a way to spot where exactly does the error happen? When debugging it goes from the console log straight to the error and I don't see how that makes sense.
To trigger the error run the snippet and click on the names.
The same functions in $('#Individuals').data('functions') can be chained and work fine when called in IndividualsList(), but not from the event listener, then the result becomes undefined.
$(document).ready(function() {
var thisWindow = $('#Individuals');
var randomNames = ['Sonia Small', 'Kurt Archer', 'Reese Mullins', 'Vikram Rayner', 'Jethro Kaye', 'Suhail Randolph', 'Kaydon Crouch', 'Jamaal Elliott', 'Herman Atkins', 'Sia Best', 'Kory Gentry', 'Fallon Sawyer', 'Zayyan Hughes', 'Ayomide Byers', 'Emilia Key', 'Jaxson Guerrero', 'Gracey Frazier', 'Millie Mora', 'Akshay Parker', 'Margareta Emiliana'];
var generatedIndividuals = [];
function generateIndividual(name) {
return {
IndividualName: name
};
}
function IndividualsList(element) {
var list = [];
this.add = function(thisIndividual) {
$('#Individuals').data('functions').init(element, list).add(thisIndividual);
}
this.refresh = function() {
$('#Individuals').data('functions').init(element, list).refresh();
}
this.sort = function(order) {
$('#Individuals').data('functions').init(element, list).sort(order);
}
}
thisWindow.data('functions', (function() {
var element = $();
var list = [];
return {
add: function(thisIndividual) {
list.push(thisIndividual);
return thisWindow.data('functions');
},
init: function(thisElement, thisList) {
element = thisElement;
list = thisList;
return thisWindow.data('functions');
},
refresh: function() {
var thisList = element.html('');
for (let i = 0; i < list.length; i++) {
thisList.append(
'<div>' + list[i].IndividualName + '</div>'
);
}
return thisWindow.data('functions');
},
sort: function(order) {
list.sort(function(a, b) {
if (a.IndividualName < b.IndividualName) return -1 * order;
if (a.IndividualName > b.IndividualName) return 1 * order;
return 0;
});
console.log(thisWindow.data('functions'));
return thisWindow.data('functions');
}
}
})());
for (let i = 0; i < 20; i++) {
let nameNum = Math.floor(Math.random() * randomNames.length);
let thisClient = generateIndividual(randomNames[nameNum]);
generatedIndividuals.push(thisClient);
}
(function() {
var targetElement = thisWindow.find('div.individuals-list');
var targetData = {}
targetElement.data('individualsList', new IndividualsList(targetElement));
targetData = targetElement.data('individualsList');
for (let i = 0; i < generatedIndividuals.length; i++) {
targetData.add(generatedIndividuals[i]);
}
targetData.refresh();
})();
thisWindow.on('click', '.individuals-list', function() {
var thisElem = $(this);
var order = parseInt(thisElem.data('order'));
thisWindow.find('div.individuals-list').data('individualsList').sort(order).refresh();
thisElem.data('order', order * (-1));
});
});
.individuals-list {
border: 1px solid;
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="Individuals">
<div class="individuals-list" data-order="1"></div>
</div>
https://jsfiddle.net/Kethus/ymgwrLhj/
You are referring to the wrong sort() function, hence call it incorrectly so it returns undefined. Then you call refresh() on undefined that was returned from sort. Here's why:
In your IFFE, you use .data() to set the data = new IndvidualsList on thisWindow.find('div.individuals-list')
This code:
thisWindow.find('div.individuals-list').data('individualsList')
Returns that instantiated IndividualsList Object:
IndividualsList = $1
add: function(thisIndividual)
refresh: function()
sort: function(fieldName, order)
IndividualsList Prototype
Note the sort() function's definition. Sort in this object requires two parameters, fieldName and order; yet you call sort() and only pass order;
This indicates your expectation for the sort() function is incorrect or the wrong sort function is being made available at that line of code (in the click handler).
How to debug
Set a breakpoint at line 132 of the provided JavaScript in the
Fiddle.
Click a name in the list.
While at the breakpoint (execution paused), move to the console and run this in the console:
thisWindow.find('div.individuals-list').data('individualsList')
Note the sort() function definition in the list of functions
Next, in the console run this statement:
thisWindow.find('div.individuals-list').data('individualsList').sort(order)
Note the return is undefined <-- This is the issue
The returned value doesn't transfer from the closure to the instance that called it, the class has to be changed like so:
function IndividualsList(element) {
var list = [];
this.add = function(thisIndividual) {
return $('#Individuals').data('functions').init(element, list).add(thisIndividual);
}
this.refresh = function() {
return $('#Individuals').data('functions').init(element, list).refresh();
}
this.sort = function(order) {
return $('#Individuals').data('functions').init(element, list).sort(order);
}
}
The breakpoint could have been in one of IndividualsList()'s methods so it can be noticed that the closure returns the desired object while the method does not. Different names for either the functions or methods would help to reinforce that they are separate.

Get object out of observable array

Why is m "undefined" in this code:
currentViewModel = ko.mapping.fromJS(viewModel);
currentViewModel.getReport = function(reportId) {
for(var i=0;i<currentViewModel.availableReports().length;i++) {
if(currentViewModel.availableReports()[i].id == reportId) {
var m = currentViewModel.availableReports()[i];
return currentViewModel.availableReports()[i];
}
}
}
I call getReport() as an onclick event and I want to send the report object to a view (modal) I can do a foreach on the availableReports and it's all there. When I run through the debugger, it loops through the array and finds the right one. But why can't I pull it out of the array? "m" remains undefined the the function returns undefined.
What am I missing here?
EDIT: there is a follow up question here:
Can knockout.js wait to bind until an onClick?
You just need to change if(currentViewModel.availableReports()[i].id ... to if(currentViewModel.availableReports()[i].id() ... because after mapping id will become an observable, i.e. function.
Updated code:
currentViewModel = ko.mapping.fromJS(viewModel);
currentViewModel.getReport = function(reportId) {
for (var i = 0; i < currentViewModel.availableReports().length; i++) {
if (currentViewModel.availableReports()[i].id() == reportId) {
var m = currentViewModel.availableReports()[i];
return currentViewModel.availableReports()[i];
}
}
}
Demo - Fiddle.
I'll repeat the solution from #NikolayErmakov's answer here, but want to add two things to get a more complete answer. You end with:
...m remains undefined and the function returns undefined.
What am I missing here?
You're missing two things:
The var m bit of the first statement inside the if is hoisted to the top of the current scope (the top of the function). This is why the debugger can tell you what m is, even if you never reach the line of code it's on.
If a function invocation reaches the end of a function (as is the case for you, since you never go inside the if) without seeing an explicit return statement, it will return undefined.
To better understand this, you should interpret your function like this:
currentViewModel.getReport = function(reportId) {
var m;
for (var i = 0; i < currentViewModel.availableReports().length; i++) {
if (currentViewModel.availableReports()[i].id == reportId) {
m = currentViewModel.availableReports()[i];
return currentViewModel.availableReports()[i];
}
}
return undefined;
}
Some people (e.g. Douglas Crockford) do recommend placing var statements at the top of a function, though it's a matter of style to some degree. I don't think many people explicitly return undefined at the end of a function, though in your case I might be explicit about that scenario and return null (or throw an Error even).
As promised, I'll repeat the actual solution, as I concur with the other answer:
you need to invoke id as a function to get its value (because the mapping plugin will map to observable()s.
In addition:
I'd retrieve the array only once
I'd suggest using === instead of ==
Here's my v0.5 version:
currentViewModel.getReport = function(reportId) {
var m = null, reports = currentViewModel.availableReports();
for (var i = 0; i < reports.length; i++) {
if (reports[i].id() === reportId) {
m = reports[i];
return m;
}
}
return m;
}
But I'd optimize it to this v1.0:
currentViewModel.getReport = function(reportId) {
var reports = currentViewModel.availableReports();
for (var i = 0; i < reports.length; i++) {
if (reports[i].id() === reportId) {
return reports[i];
}
}
return null;
}
For completeness, here's another version that utilizes filter on arrays:
currentViewModel.getReport = function(reportId) {
var reports = currentViewModel.availableReports().filter(function(r) { return r.id() === reportId; });
return reports.length >= 1 ? reports[0] : null;
}

Pushing object into array erases instead of adding

I have a function like this :
$scope.saveSearch = function () {
var alreadyExist = false;
for (var i = 0; i < $scope.savedSearch.length; i++) {
if (JSON.stringify($scope.searched) === JSON.stringify($scope.savedSearch[i])) {
alreadyExist = true;
break;
}
}
if (!alreadyExist) {
$scope.savedSearch.push($scope.searched);
localStorage.setItem("savedSearch", JSON.stringify($scope.savedSearch));
}
};
Before that : $scope.savedSearch = [];
$scope.searched = {
IS: "",
area: "",
block: "",
type: "",
level: ""
};
The values in $scope.searched object are initialized and then modified by the user.
My problem is :
$scope.savedSearch always contains only the last pushed object. Instead of adding the object to the array, it just replaces the current object.
I don't understand why.
You'll want to change your push line to:
$scope.savedSearch.push(angular.copy($scope.searched));
I believe your problem is that objects are passed by reference. Since the object you have in the savedSearch is always pointing to the exact object you're searching, alreadyExist will always be true.
My guess is that the object reference is being stored in your array, not the actual object itself. Because of this, any subsequent calls to push the object to your array will not work because the object reference already exists in the array. It's merely updated.
Try this instead. Use angular.copy() to create a deep copy of the object and push the copy to your array. See if that works.
if (!alreadyExist) {
$scope.savedSearch.push(angular.copy($scope.searched));
localStorage.setItem("savedSearch", JSON.stringify($scope.savedSearch));
}
You are pushing the Object outside of the for so only 1 element get pushed in try move it inside the for and every object which doesnt already exist will be pushed in
$scope.saveSearch = function () {
var alreadyExist = false;
for (var i = 0; i < $scope.savedSearch.length; i++) {
if (JSON.stringify($scope.searched) === JSON.stringify($scope.savedSearch[i])) {
alreadyExist = true;
break;
}
if (!alreadyExist) {
$scope.savedSearch.push($scope.searched);
localStorage.setItem("savedSearch", JSON.stringify($scope.savedSearch));
}
}
};
easier way would be to just
$scope.saveSearch = function () {
var alreadyExist = false;
for (var i = 0; i < $scope.savedSearch.length; i++) {
if (JSON.stringify($scope.searched) != JSON.stringify($scope.savedSearch[i])) {
$scope.savedSearch.push($scope.searched);
localStorage.setItem("savedSearch", JSON.stringify($scope.savedSearch));
}else{
break
}
}
};

Missing prototype methods in controller

I'm retrieving plain json data via my service and then attaching some behaviour to it via a constructor and prototype methods.
When this object is returned from the service to the controller, I can see via developer tools that it has the correct 'class' name, however none of the methods are available.
When testing my classes and protoypes outside of an angularjs app, they work as expected.
The below is my service:
app.factory('characterService', function($http) {
var _chars = [];
return {
getCharacters: function() {
return $http.get('app/resources/charsheets.js').then(function(d) {
_chars = [];
var data = d.data.characters;
for (var i = 0; i < data.length; i++) {
_chars.push(new Character(data[i]));
}
return _chars;
});
}
};
Character.prototype.getStatBonus = function(statAbbr) {
var bonus = 0;
if (statAbbr && statAbbr != '') {
var baseStats = this["baseStats"];
for (var i = 0; i < baseStats.length; i++) {
if (baseStats[i].abv == statAbbr) {
//More stuff normally happens, but for the sake of brevity
//it has been reduced to a simple assignment
bonus = baseStats[i].totalBonus;
break;
}
}
}
return bonus;
};
});
function Character(raw) {
//Stuff here
}
My very basic controller:
app.controller('characterController',function($scope,characterService)
{
$scope.model = {};
characterService.getCharacters().then(function(data){
$scope.model.characters = data;
$scope.model.currentCharacter = data[0];
});
});
Some sample markup:
<div>
{{model.currentCharacter.getStatBonus('Ag')}}
</div>
The error I am getting is that getStatBonus is undefined, even though the object is of the Character type (confirmed via developer tools).
Is what I'm attempting to do even possible?
Thanks
Found my problem... I was declaring the constructors and prototype methods in the body of the service.
I declared them outside of it and it works but not sure if that is the angular way of doing things

javascript jquery function is this somehow wrong?

function rebuildJSONObject(){
$.getJSON('services.json', function(data) {
//stof start
var input = data;
var output = { myservices: [] };
for (var key in input) {
if (input.hasOwnProperty(key)) {
for (var i = 0, hostsinfo = input[key].hostsinfo; i < hostsinfo.length; i++) {
output.myservices.push({
'nametag': key,
'hostidn': hostsinfo[i]['hostidn'],
'details': hostsinfo[i]['details'],
'currstatus': hostsinfo[i]['currstatus'],
'currstatusclass': hostsinfo[i]['currstatusclass']
});
}
}
}
//stof end
return output;
});
}
//setting it for use later in the script
var serviceJSONObject = rebuildJSONObject();
I know the stuff going on in the function is working properly cause if I apply it to a click event it works charming. However I would rather load the JSON object into memory once and work with it client side there after unless saved. My Problem is however anywhere I call "serviceJSONObject" I get an "undefined" error.
So How am I doing this wrong and how would I define a variable like this early in the game so the rest of the script can use said variable.
The issue is that output is returned before the callback function is called. You should be able to save the value to serviceJSONObject by using a closure:
function rebuildJSONObject(serviceJSONObject){
$.getJSON('services.json', function(data) {
//stof start
var input = data;
// Use the serviceJSONObject that is passed into rebuildJSONObject
serviceJSONObject = { myservices: [] };
for (var key in input) {
if (input.hasOwnProperty(key)) {
for (var i = 0, hostsinfo = input[key].hostsinfo; i < hostsinfo.length; i++) {
serviceJSONObject.myservices.push({
'nametag': key,
'hostidn': hostsinfo[i]['hostidn'],
'details': hostsinfo[i]['details'],
'currstatus': hostsinfo[i]['currstatus'],
'currstatusclass': hostsinfo[i]['currstatusclass']
});
}
}
}
//stof end
});
}
//setting it for use later in the script
var serviceJSONObject;
rebuildJSONObject(serviceJSONObject);
Why not add a cache property to a function that will store the result of the initial output (loaded via ajax) and returning the saved state to any consecutive call.
function rebuildJSONObject(callback) {
var self = this;
if (typeof self.cache !== 'undefined') {
if (typeof callback === 'function') {
callback(self.cache);
}
return;
}
$.getJSON('services.json', function(data) {
//stof start
var input = data,
output = { myservices: [] };
for (var key in input) {
if (input.hasOwnProperty(key)) {
for (var i = 0, hostsinfo = input[key].hostsinfo; i < hostsinfo.length; i++) {
output.myservices.push({
'nametag': key,
'hostidn': hostsinfo[i]['hostidn'],
'details': hostsinfo[i]['details'],
'currstatus': hostsinfo[i]['currstatus'],
'currstatusclass': hostsinfo[i]['currstatusclass']
});
}
}
}
//stof end
self.cache = output;
if (typeof callback === 'function') {
callback(self.cache);
}
return;
});
}
EDIT: For the first time you will need to call this function asynchronously and supply a callback function, for example
rebuildJSONObject(function(output) {
/*
* Process your output here
*/
console.log(output);
});
Each consecutive time you can again use it synchronously:
console.log(rebuildJSONObject.cache);
There are a couple of problems with this.
The call to getJSON is asynchronous so you need to be careful you don't try to use the results before the call has returned your results.
The way it is at the moment, the results will not be returned to serviceJSONObject. The return output statement is setting the return for the anonymous function, not the return value for rebuildJSONObject, so the results will just disappear. If you want the results to be available elsewhwere in code you will either need to store them in a global variable or access them inside the callback.

Categories

Resources