angular, ng-class with function inside repeat - javascript

I'm trying to put a class on an input if it meets certain requirements and am having problems
it looks like so -
ng-class="'isPartial': canPartial($index)"
This is inside a repeat, the function it's referring to looks like so
$scope.canPartial = function(index) {
var needsMet = _.reduce($scope.allAccounts[index].schools, function (memo, schools) {
return memo + (schools.selected ? 1 : 0);
}, 0);
console.log(needsMet);
return (needsMet === $scope.allAccounts[index].schools.length);
};
so it's using underscore.js to check if all its children are checked. I know the function works correct, however my issue is passing it as the condition for the ng-class. So if it returns true it will add the class. I'm getting a $parse.syntax error and I cannot seem to figuire out why because I se other examples of ng-class using a function. Perhaps it's because I'm trying to pass the $index, however it is inside a repeat, but I don't know if that causes an issue.
Any help would be much appreciated. Thanks!

Your ng-class expression is invalid.
Change your ng-class declaration in order to take an object as a value:
ng-class="{'isPartial': canPartial($index)}"

Related

ES6 calling method in another class, using modules

I know there is many questions like this asked, but I have been searching for hours and can't find any answers. I have this method, which takes in a parameter, which should be ID of two selects. Using this parameter, I want to determine which select is used and execute the if statement, but to no avail. When I run it, it shows no errors in console in Chrome and it does nothing. Can anyone shed some light on it, this is the method in one export class:
static styleCircle(select) {
if(this.select === ELEMENTS.ELEMENT_COLOR_SELECT) {
var getColor = ELEMENTS.ELEMENT_COLOR_SELECT;
var colorValue = getColor.options[getColor.selectedIndex].value;
ELEMENTS.ELEMENT_STYLE_CIRCLE.style.backgroundColor = colorValue;
} else if(select == ELEMENTS.ELEMENT_BORDER_SELECT) {
var getRadius = ELEMENTS.ELEMENT_BORDER_SELECT;
var radiusValue = getRadius.options[getRadius.selectedIndex].value;
ELEMENTS.ELEMENT_STYLE_CIRCLE.style.borderRadius = radiusValue;
}
}
This is it being called in another class, on two select elements, and the class is imported at the top of the file:
ELEMENTS.ELEMENT_COLOR_SELECT.onchange = Script.styleCircle(this);
ELEMENTS.ELEMENT_BORDER_SELECT.onchange = Script.styleCircle(this);
ELEMENTS is a file with constants, which are just being used to get ID's from the HTML file. I used other methods like this, with onclick events, but none had parameters, and now I'm stuck here. Thanks in advance.
You don't want to call the functions right now but instead you probably want to pass functions. Through that you can access the proper this and pass it to styleCircle:
ELEMENTS.ELEMENT_COLOR_SELECT.onchange = function() {
Script.styleCircle(this);
};
ELEMENTS.ELEMENT_BORDER_SELECT.onchange = function() {
Script.styleCircle(this);
};
Additionally this.select is probably causing you troubles as window.select is undefined.
First step would be to try debugging and ensure select is equivalent to either of those constants. Make sure you have full branching coverage in your debugging. That would mean start by adding an else statement to that if/else if statement - it's possible that your select is not equal to either constant and so neither branch is run.

Angular ngClass is not updating my classes even though my condition change as expected

I have something like this in a template I am creating
<div ui-view id="app" class="nng-3" ng-class="{ 'app-mobile': app.isMobile, 'app-navbar-fixed': app.layout.isNavbarFixed, 'app-sidebar-fixed': app.layout.isSidebarFixed, 'app-sidebar-closed': app.layout.isSidebarClosed, 'app-footer-fixed': app.layout.isFooterFixed }"></div>
The values app.layout.isNavbarFixed, etc are initialized with either zero or one, and for the first time the page loads the appropriate classes are inserted into my div. Any change after that though, by means of a button that sets those values, is not reflected on my class attributes by ng-class.
If I directly print those variables in my template, eg. {{app.layout.isSidebarFixed}} I can see them changing from true to false and vice versa, but ng-class will not update or remove any new classes.
I am not sure where to begin and look for the solution for this since with my limited knowledge I cant spot any obvious mistake immediately. Does anyone have any idea on what causes this issue?
A workaround of mine is to manipulate a model variable just for the ng-class toggling:
1) Whenever my list is empty, I update my model:
$scope.extract = function(removeItemId) {
$scope.list= jQuery.grep($scope.list, function(item){return item.id != removeItemId});
if (!$scope.list.length) {
$scope.liststate = "empty";
}
}
2) Whenever my list is not empty, I set another state
$scope.extract = function(item) {
$scope.list.push(item);
$scope.liststate = "notempty";
}
3) I use this additional model on my ng-class:
ng-class="{'bg-empty': liststate == 'empty', 'bg-notempty': liststate == 'notempty'}"
Update*: Also you can add any other states if needed and use at ng-class like:
ng-class="{'bg-empty': liststate == 'empty', 'bg-notempty': liststate == 'notempty', 'bg-additional-state', liststate == 'additional-state'}"
Because you know, an initially empty list state is not equal with a list which is emptied by command.
Probably the ng-class implementation is not considering "0" to be "false" as you expect, because it's doing an strict comparision with ===.
Try expressing the conditions like this:
ng-class="{ 'app-mobile': app.isMobile == 0, 'app-navbar-fixed': app.layout.isNavbarFixed == 0, ...
Tried your variant. Have everything working. Please, check if your button click event is on $scope and AngularJS knows, that values changed.
For example, if function triggered by native DOM Event (some jQuery table updated or something) than you should use $apply function, to reflect changes on scope. Something like this:
$scope.eventHandler = function(e) {
$scope.$apply(function(){ $scope.someProp = e.value;}
}
In the mean time, check this jsfiddle
Update:
Please check this jsfiddle out. This works in AngularJS 1.4.8, but doesn't in other, that support jsfiddle. From what I know, it's not really a best idea to do an assignment inside of expression, the controller is meant for this thing.

Modifying scope object values in checkbox using ngchange

Forgive if this is a newbie error but I think this must be really simple and I am obviously missing something..
I have the following ngrepeat:
<div class="panel-body" data-ng-repeat="participant in activity.Participants" ng-show="showp" ng-init="participant.CheckInTime ='not set'"> <--The 'init' is for debug
{{getparticipantName(participant.ParticipantID)}}
Check in = {{participant.CheckInTime}}
<input type="checkbox" ng-model="participant.CheckedIn"
ng-change="setToNowOrNull(participant.CheckedIn, 'participant.CheckInTime')">
<br />
Check in = {{participant.CheckInTime}}
</div>
Which is nested within another ng-repeat which defines the controller etc. and that works fine. But when I click the checkbox, the settoNowOrNull function gets called, changes the value as it should, but this isn't returned to the participant.CheckInTime .. here is the function:
$scope.setToNowOrNull = function (deciderbool, thingtoset) {
if (deciderbool) //its been set to true.
{
$scope[thingtoset] = Date.now();
}
else //its been cleared to false
{
$scope[thingtoset] == null;
}
}
I added the $scope[thingtoset] after reading another question on here but to no avail.. the same with the single quotes around 'participant.CheckInTime' in the ng-change line. (This is supposed to pass the object not the value?)..
I'm obviously not getting something, and I'd have thought I could have done it in the html angular anyway rather than needing to call the controller for something so trivial - I just need to record the Date.Now() into the participant.CheckInTime. Thoughts anyone?
You can try this:
<input type="checkbox" ng-model="participant.CheckedIn"
ng-change="setToNowOrNull(participant)">
$scope.setToNowOrNull = function (participant) {
if (participant.CheckedIn) //its been set to true.
{
participant.CheckInTime = Date.now();
}
else //its been cleared to false
{
participant.CheckInTime == null;
}
}
There are several problems with your approach, particularly with
$scope[thingtoset] = Date.now();
Using value being passed into function this would be
$scope['participant.CheckInTime'] = Date.now();
This isn't the same as
$scope.participant.CheckInTime;
The whole string represents one object key and would have to be parsed into parts to get 2 levels out of it. Essentially it's invalid syntax for what you had hoped to accomplish
Even if it was valid there is no such object on your scope since participant is an alias for an object in the view. That object is within the array activity.Participants.
In conclusion, pass the actual object into your function and work with the whole object
If you want a generic method for multiple properties it would need to be more like:
$scope.setToNowOrNull = function (object, key){
switch(key){
case 'CheckInTime':
// code for this key
break;
}
}
Then in markup would use:
<input ng-change="setToNowOrNull(participant, 'CheckInTime')">
I suspect that you likely don't need such a generic method and can simply pass a single argument, the participant object, for that specific change handler
Within the ng-repeat, the object has its own scope, therefore when you try to write into $scope[thingtoset], instead of overriding the parent scope's corresponding object, you just create another object with the same name in the lower scope. Instead you can create an object in the upper scope, say $scope.thingHolder, and when this function is called, it can update $scope.thingHolder[thingtoset]. Then you will be able to observe the changes correctly in the upper scope too.

Using a parameter with a Javascript object?

Currently I can do:
function addStat() {
player.str = player.str + 1;
}
But I want to be able to use things other than just "str" with my player object. So I decided with doing something like this:
function addStat(stat) {
player.stat = player.stat + 1;
}
But that doesn't seem to work, iv'e tried looking up the syntax for using parameters but could not find anything similar to the way I need.
I learned about "this" but I can't get it to work with my function.
I thought this:
function addStat(thing, stat) {
thing.stat = thing.stat + 1;
statReset();
}
would work but I can see why it won't. I made sure the rest of my javascript and html work and when I add those functions nothing breaks, it just doesn't work.
When assigning properties with a variable, you need to use bracket notation, as opposed to dot notation. This, then, looks like:
function addStat(stat) {
(stat in player) ? ++player[stat] : player[stat] = 1;
}
Due to comments (that I disagree with), I figured I should mention that since you are attempting to modify a property that may not exist, you should also add a safety check to see if you can modify it.
Otherwise you will be modifying undefined, and that will cause undesired output..
You can access properties with []:
function addStat(prop) {
player[prop] = player[prop] + 1;
}
so calling addStat("stat") will actually set player.stat.
In javascript, the syntax
object.key
is equivalent to
object["key"]
So your thing.stat is equivalent to thing["stat"], i.e. the key is the literal string "stat" when what you really want is to use the value referenced by the parameter stat as the key:
thing[stat] = thing[stat] + 1;

Using a watch inside a link is causing an infinite digest cycle.

I'm trying to write a directive that associates a score with a color.
I've made an attempt already, and the Plunker is here. The directive itself is here:
.directive('scorebox', function () {
function link ($scope, $elem, $attr) {
var one = 1;
$scope.$watch('[score,ptsPossible]', function (newValue) {
pctScore = newValue[0] / newValue[1]
if (pctScore <= 0.4) {
rating = 'low';
} else if (pctScore <= 0.6) {
rating = 'med';
} else if (pctScore <= 0.8) {
rating = 'high';
} else if (pctScore == 1) {
rating = 'perfect';
}
$elem.removeClass();
$elem.addClass('scorebox');
$elem.addClass(rating);
$elem.text(newValue[0] + "/" + newValue[1]);
});
};
return {
restrict: 'E',
scope: {
score: "=",
ptsPossible: "="
},
link:link
}
})
I've got a couple of problems.
First, it's pretty obvious to me that I'm not supposed to do a $watch inside a link function. I'm creating an infinite digest cycle, and that's not good. I'm still not sure why, though.
I'm not manipulating the DOM correctly. Even though I'm calling $elem.removeClass(), it's not working--the element retains any classes it had before.
What is the right way to do this?
As #miqid said, no need to $watch both score and ptsPossible, since you only want to react when score changes (at least in this situation you are presenting).
The problem here, is you are using jqLite's removeClass function instead of jQuery's. If jQuery is not included before Angular in the code, Angular will instead use jqLite functions, which is like a smaller, much simpler version of jQuery. It is also, slightly different. jQuery's removeClass(), will remove all classes is no parameter is passed. jqLite will not do the same, it will just remove those classes that you pass as parameter.
You never included jQuery at all, so that's what's happening. Here is the edited Plunker. You can check jQuery is now included in the top, and everything works as expected. And also the $watch is much simpler.
Just a suggestion to get things working:
There's no need to $watch both score and ptsPossible since the latter never changes after the value is loaded from its corresponding attribute value. You also have access to scope variables inside the $watch callback function.
That's unusual as I would've expected your removeClass() to work as well. You could instead try removeAttr('class') here in the meanwhile.
Here's a Plunker with the suggested changes.
You need to use $watchCollectionand not $watch. You are passing in an array to $watch which is how $watchCollection expects.

Categories

Resources