I have a massive validate function in my backbone model that could be simplified if only I could detect the attribute I'm testing against. There are a few ideas of how I can think to approach this problem, but they all rely on knowing the attribute name I'm testing, without hardcoding it as I am in the id variable below.
Here is an example:
validate : function(attr){
var t = this;
if(attr.user1DobMonth && attr.user1DobMonth != t.get('user1DobMonth')){
var val = jQuery.trim(attr.user1DobMonth.toLowerCase()),
id = 'user1DobMonth',
error = {
attr : id
};
if(val === 'select'){
return error;
}
}
if(attr.user2DobMonth && attr.user2DobMonth != t.get('user2DobMonth')){
var val = jQuery.trim(attr.user2DobMonth.toLowerCase()),
id = 'user2DobMonth',
error = {
attr : id
};
if(val === 'select'){
return error;
}
}
}
Related
I have some JavaScript code that gives this error:
Uncaught TypeError: Cannot read property 'value' of undefined
Here is my code:
var i1 = document.getElementById('i1');
var i2 = document.getElementById('i2');
var __i = {'user' : document.getElementsByName("username")[0], 'pass' : document.getElementsByName("password")[0] };
if( __i.user.value.length >= 1 ) { i1.value = ''; } else { i1.value = 'Acc'; }
if( __i.pass.value.length >= 1 ) { i2.value = ''; } else { i2.value = 'Pwd'; }
What does this error mean?
Seems like one of your values, with a property key of 'value' is undefined. Test that i1, i2and __i are defined before executing the if statements:
var i1 = document.getElementById('i1');
var i2 = document.getElementById('i2');
var __i = {'user' : document.getElementsByName("username")[0], 'pass' : document.getElementsByName("password")[0] };
if(i1 && i2 && __i.user && __i.pass)
{
if( __i.user.value.length >= 1 ) { i1.value = ''; } else { i1.value = 'Acc'; }
if( __i.pass.value.length >= 1 ) { i2.value = ''; } else { i2.value = 'Pwd'; }
}
Either document.getElementById('i1'), document.getElementById('i2'), or document.getElementsByName("username")[0] is returning no element. Check, that all elements exist.
Try this, It always works, and you will get NO TypeError:
try{
var i1 = document.getElementById('i1');
var i2 = document.getElementById('i2');
var __i = {'user' : document.getElementsByName("username")[0], 'pass' : document.getElementsByName("password")[0] };
if( __i.user.value.length >= 1 ) { i1.value = ''; } else { i1.value = 'Acc'; }
if( __i.pass.value.length >= 1 ) { i2.value = ''; } else { i2.value = 'Pwd'; }
}catch(e){
if(e){
// If fails, Do something else
}
}
First, you should make sure that document.getElementsByName("username")[0] actually returns an object and not "undefined". You can simply check like
if (typeof document.getElementsByName("username")[0] != 'undefined')
Similarly for the other element password.
The posts here help me a lot on my way to find a solution for the Uncaught TypeError: Cannot read property 'value' of undefined issue.
There are already here many answers which are correct, but what we don't have here is the combination for 2 answers that i think resolve this issue completely.
function myFunction(field, data){
if (typeof document.getElementsByName("+field+")[0] != 'undefined'){
document.getElementsByName("+field+")[0].value=data;
}
}
The difference is that you make a check(if a property is defined or not) and if the check is true then you can try to assign it a value.
You can just create a function to check if the variable exists, else will return a default value :
function isSet(element, defaultVal){
if(typeof element != 'undefined'){
return element;
}
console.log('one missing element');
return defaultVal;
}
And use it in a variable check:
var variable = isSet(variable, 'Default value');
You code looks like automatically generated from other code - you should check that html elements with id=i1 and i2 and name=username and password exists before processing them.
I would like to create a filter on nested objects like this:
Object 1 :
property1
property2
property3
children : Child 1 :
propertyChild1
propertyChild2
Child 2 :
And so on. An object can have multiple child. There is no depth limit specified.The problem is that I want to search only on certain properties of the object so I used:
ng-repeat="groupLevel1 in groupLevel2.children | filter: {lineDescription: searchKeyword}"
This is searching on all levels but if a parent does not contain the searchKeyword, all the children (which may contain the search) aren't displayed. I want that all parent levels are displayed in order to display the children that contains the search keyword even if the parents do not match the search.
I tried some complicated script but it does not work:
appReportingHoliday.filter('globalFilter', function(){
return function(array, predicate){
return array.filter(function(val){
var formattedObj = parseFloatInternational(predicate);
var re = new RegExp(formattedObj, 'i');
var initialArray = [];
initialArray.push(val);
var childIsNeeded = false;
var toReturnTemp;
var parents = [];
var toReturn = [];
while(initialArray!=null){
angular.forEach(initialArray, function (currentVal) {
toReturnTemp = false;
//We check if the val is concerned by the search
toReturnTemp = re.test(currentVal.lineDescription) || re.test(currentVal.acquiredHolidays) || re.test(currentVal.tokenHolidays) || re.test(currentVal.availableHolidays)
|| re.test(currentVal.dailyCost) || re.test(currentVal.valuation);
if (toReturnTemp) {
//if it is, we need to add the result to the toReturn array and also the parents that we could have saved in the according array
toReturn.push(currentVal);
toReturn.push(parents);
parents = [];
}
else {
//else we save it in the parents array if a child is needed
if(currentVal.children!=null) {
parents.push(currentVal);
}
}
var index = initialArray.indexOf(currentVal);
initialArray.splice(index, 1);
if(currentVal.children!=null) {
angular.forEach(currentVal.children, function (currentChild) {
initialArray.push(currentChild);
});
}
});
if(initialArray.length==0) initialArray = null;
}
return toReturn;
});
}
});
The display is made like this:
<tr class="groupReportingTreeDatatable" ng-repeat-start="groupLevel3 in myData | filter: {lineDescription: searchKeyword}" ng-init="$index < 2 ? groupLevel3.hideRows = false : groupLevel3.hideRows = true;" ng-class-even="'dataTable_row1'" ng-class-odd="'dataTable_row2'" spinner-handler-directive="">
...
<tr class="groupReportingTreeDatatable" ng-hide="groupLevel3.hideRows" ng-init="groupLevel2.hideRows = true" ng-repeat-start="groupLevel2 in groupLevel3.children | filter: {lineDescription: searchKeyword}" ng-class-even="'dataTable_row1'" ng-class-odd="'dataTable_row2'">
...
<tr ng-hide="groupLevel2.hideRows || groupLevel3.hideRows" ng-repeat="groupLevel1 in groupLevel2.children | filter: {lineDescription: searchKeyword}" ng-class-even="'dataTable_row1'" ng-class-odd="'dataTable_row2'" ng-repeat-end="">
EDIT :
I tried something else which works for some searches but not all of them :(
appReportingHoliday.filter('globalFilter', function() {
return function (array, predicate) {
return array.filter(function (val) {
var formattedObj = parseFloatInternational(predicate);
var re = new RegExp(formattedObj, 'i');
var found = re.test(val.lineDescription) || re.test(val.acquiredHolidays) || re.test(val.tokenHolidays) || re.test(val.availableHolidays)
|| re.test(val.dailyCost) || re.test(val.valuation);
var child = val.children;
while(child!=null && found == false){
angular.forEach(child, function (currentChild) {
if(found == false) {
console.log(currentChild.lineDescription)
found = re.test(currentChild.lineDescription) || re.test(currentChild.acquiredHolidays) || re.test(currentChild.tokenHolidays) || re.test(currentChild.availableHolidays)
|| re.test(currentChild.dailyCost) || re.test(currentChild.valuation);
}
});
child = child.children;
}
return found;
});
}
});
Wouldn't it be easier to have a second variable where you flatten all of the children of the first one? You could define a recursive function doing that, something like...
var mainObject = ... ;//that's your object
var flattened = new Array();
function flatten(main,flat) {
var children = main.children;
for (var i=0;i<children.length;i++) {
flatten(children[i],flat); // recursive call, depth-first
delete children[i].children; // those are already treated
flat.push(children[i]); // add children
}
delete main.children;
flat.push(main);
}
Now you can filter on the properties directly.
I don't see how to integrate this in my code. I cannot change the base variable because I need it in this structure to display it on the screen. I can create another flattened array but the filter has to be applied on the var I display, no ? So I cannot use the flattened var. I have to admit I am a bit lost ^^
I fixed the depth to 3 to make it easier so now I have a this:
appReportingHoliday.filter('globalFilter', function() {
return function (array, predicate) {
return array.filter(function (val) {
var formattedObj = parseFloatInternational(predicate);
var re = new RegExp(formattedObj, 'i');
var found = re.test(val.lineDescription) || re.test(val.acquiredHolidays) || re.test(val.tokenHolidays) || re.test(val.availableHolidays)
|| re.test(val.dailyCost) || re.test(val.valuation);
var child = val.children;
if(child!=null && found == false){
angular.forEach(child, function (currentChild) {
if(found == false) {
found = re.test(currentChild.lineDescription) || re.test(currentChild.acquiredHolidays) || re.test(currentChild.tokenHolidays) || re.test(currentChild.availableHolidays)
|| re.test(currentChild.dailyCost) || re.test(currentChild.valuation);
}
if(currentChild.children!=null && found == false){
angular.forEach(currentChild.children, function (currentGrandChild) {
if(found == false) {
found = re.test(currentGrandChild.lineDescription) || re.test(currentGrandChild.acquiredHolidays) || re.test(currentGrandChild.tokenHolidays) || re.test(currentGrandChild.availableHolidays)
|| re.test(currentGrandChild.dailyCost) || re.test(currentGrandChild.valuation);
}
});
}
});
child = child.children;
}
return found;
});
}
});
The only problem that remains is that if the search is on a parent, I want all the child t be displayed but right now only the matched children are displayed :s I cannot find the parent from a child, I have only the link from parent to child not the other way around :s
I am following this approach to filter nested json response. I have a nested property like this:
instances:{
instance:[
{cname:'name1', location:'pa', price:40, model:'2014' },
{cname:'name1', location:'ga', price:30 , model:'2014'},
{cname:'name1', location:'ga', price:20, model:'2010' }
]}
I can filter by top level properties using the above mentioned example but not the child properties.
I have modified above example to show nested properties of my json here.http://jsfiddle.net/jackAndy/qygL2m01/4/. I am new to angularjs.
First of all - why You use instances.instance? It it not principally, use players.instances = [];
Use Group functions only 1 time after data loading; Watching filters - it's not necessary in this case;
Function for get filters values (I use underscore uniq function, You can use Your own algorithm for this):
$scope.getFieldsValues = function(field){
var result = [];
for(var i = 0; i < $scope.players.length; i++){
result.push($scope.players[i][field]);
}
return _.uniq(result);
};
Filter for players:
$scope.testFl = function(el){
for(var filter in $scope.filters){
var filterArray = [];
for(var i in $scope.filters[filter]){
if($scope.filters[filter][i]) filterArray.push(i);
}
//You can make array with instances properties & compare with it;
if(filter === 'location'){
if(el.instances && el.instances.length > 0){
var intersection = el.instances.filter(function(n) {
return filterArray.indexOf(n[filter]) != -1
});
} else if(filterArray.length > 0){return false;}
} else {
if(filterArray.length > 0 && filterArray.indexOf(el[filter]) === -1) return false;
}
}
return true;
};
Template:
<li ng-repeat="player in players | filter:testFl" >
Filter for instances:
$scope.testFl2 = function(el){
var filterArray = [];
for(var i in $scope.filters.location){
if($scope.filters.location[i]) filterArray.push(i);
}
return filterArray.length > 0 && filterArray.indexOf(el.location) === -1 ? false : true;
};
Template:
<span ng-repeat="loc in player.instances | filter:testFl2" >
Fiddle for this;
UPDATE:
Function for count:
$scope.getCount = function(field, value){
var obj = {};
obj[field] = value;
return _.where($scope.players, obj).length;
};
Update fiddle - update underscore, add count function;
I hope this will help you;
For answer were used:
Add underscore to jsfiddle;
variable property name in where underscore.js;
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
Im in the process of converting a knockout app to angular, I currently get an array of objects from the server but I would like to extend each object by adding some extra properties.
In knockout I would do the following:
var mappedResults = ko.utils.arrayMap(results, function(item) {
item.selected = ko.observable(true);
item.viewPreview = ko.observable(false);
return new reed.search.Candidate(item, self.viewModel.fileDownloadFailCookieName);
});
and the Candidate viewmodel:
reed.search.Candidate = function(data, fileDownloadFailCookieName) {
debugger
if (data == null) {
throw 'Error: cannot initiate candidate';
}
this.fileDownloadFailCookieName = fileDownloadFailCookieName;
this.candidateId = data.CandidateId;
this.name = data.Name;
this.surname = data.Surname;
this.forename = data.Forename;
this.displayLocation = data.DisplayLocation;
this.lastJobDetails = data.LastJobDetails;
this.displayPayRate = data.DisplayPayRate;
this.lastSignIn = data.LastSignIn;
this.downloadCVUrl = data.DownloadCVUrl;
this.additionalInfo = data.AdditionalInfo;
this.isAvailable = (data.IsAvailable) ? "Availability confirmed" : "";
this.availableMornings = data.AvailableMornings;
this.availableAfternoons = data.AvailableAfternoons;
this.availableEvenings = data.AvailableEvenings;
this.availableWeekends = data.AvailableWeekends;
this.availableShiftWork = data.AvailableShiftWork;
this.availableNights = data.AvailableNights;
this.availabilityUpdatedOn = data.AvailabilityUpdatedOn;
this.availabilityUpdatedOnDate = "| <strong>Availability updated</strong> " + data.AvailabilityUpdatedOn;
this.isAvailableForSomething =
this.availableMornings
|| this.availableAfternoons
|| this.availableEvenings
|| this.availableWeekends
|| this.availableShiftWork
|| this.availableNights;
this.viewPreview = ko.observable(false);
this.selected = ko.observable(false);
this.hasBeenNotified = ko.observable(false);
this.select = function() {
this.selected(true);
};
this.deSelect = function() {
this.selected(false);
};
this.HasFlagSet = function(availability) {
return availability ? "availabilitySelected" : "availabilityNotSelected";
};
this.ajaxCvDownload = function() {
var path = window.location.href,
iframeError,
cookieName = this.fileDownloadFailCookieName;
// download path
path = path.match(/(.+\/)/ig)[0];
if (path.match(/home/ig)) {
path = path.replace('home', this.downloadCVUrl);
} else {
path = this.downloadCVUrl;
};
$('<iframe />').attr('src', path)
.hide()
.appendTo('body').load(function() {
var message = decodeURIComponent(reed.shared.utils.getCookie(cookieName));
message = message.replace(/\+/g, " ");
if (message.length > 0 && message != "null") {
reed.shared.utils.showMessage(message, "Download Failed");
}
});
}
}
how can I achieve the same functionality in angular?
You don't need angular for this array itself contains a map function and all modern browsers support it.
var mappedResults = results.map(function(item) {
item.selected = true;
item.viewPreview = false;
return new reed.search.Candidate(item,
self.viewModel.fileDownloadFailCookieName);
});
Some other things you can improve. Firstly if you are using webapi to return data, use a formatter that fixes casing.Check this blog http://blogs.msmvps.com/theproblemsolver/2014/03/26/webapi-pascalcase-and-camelcase/
Once you have the formatter lines such as these are not required
this.surname = data.Surname;
You can then use angular.extend to copy properties into your class.