How to combine ng-class checks and ng-click comparisons? - javascript

I have an ng-repeat where the first item needs to have the class selected, as well as clicking on each item then changes the selection.
<li ng-repeat="ticker in tickers"
data-ng-class="{'selected':$first}"
ng-click="selectTicker(ticker.ticker)">{{ticker.ticker}}</li>
^ This works to have the first item have the class of selected by default.
<li ng-repeat="ticker in tickers"
data-ng-click="toggleTicker.item = $index"
data-ng-class="{'selected':toggleTicker.item == $index}"
ng-click="selectTicker(ticker.ticker)">{{ticker.ticker}}</li>
^ Thanks to this question. The above works to add the class of selected based on click, but the first item is no longer selected by default.
My question is how to combine both? I've tried the following which did not work:
data-ng-class="{'selected':$first && toggleObject.item == $index}"
My controller:
var vs = $scope;
ApiFactory.getWatchList().then(function(data) {
vs.tickers = data.data.tickers;
vs.toggleTicker = {item: -1};
vs.toggleTicker.item = vs.tickers[0];
vs.loadingDone = true;
console.log(vs.tickers[0]);
});

You are initializing item to the object at index 0 in your controller, but everywhere else it seems to be a number. If you just want to use the index (as your click sets it to the number), then try this:
var vs = $scope;
ApiFactory.getWatchList().then(function(data) {
vs.tickers = data.data.tickers;
vs.toggleTicker = {item: 0}; // set index 0 as the selected index
vs.loadingDone = true;
//console.log(vs.tickers[0]); // what if there are no items?
});
html:
<li ng-repeat="ticker in tickers"
data-ng-click="toggleTicker.item = $index"
data-ng-class="{'selected':toggleTicker.item == $index}"
{{ticker.ticker}}
</li>
Using $first is bad because the same item will always be the first one (unless you re-order them). Using ng-click and data-ng-click is bad because they are essentially identical, maybe only one will actually get called?

That's not done in ng-class, but in the controller or ng-init:
data-ng-init="toggleObject.item = tickers[0]"
data-ng-class="{'selected':toggleObject.item == $index}"
I still suggest you do it in the controller.

Related

On click select first item on a list

I have a list, I want to select the first item of my list when I select the last item.
Is there a way to do it ?
Is there a way to handle this please ?
<ion-select interface="popover" [ngModel]="selecteduser._id" (ngModelChange)="selectUser($event)" (ionChange)="onChange($event)">
<ion-option *ngFor="let user of users" [value]="user._id">
</ion-content>{{ getUserNickname(user }} </ion-option>
<ion-option>Configuration</ion-option>
</ion-select>
So I want when I click on my configuration, it will select the first item of my list
There is my ts
openConfigPage() {
this.selectedUser = this.users[0];
this.navCtrl.push(ConfigPage)
}
selectUser(userId: string) {
this.selectedUser = this.users.find(b => b._id === userId)
this.onSelect.emit(this.selectedUser)
}
Is there a way to do it with the DOM ?
To me, it seems like not much information is being present here. Don't let this discourage you from asking questions in the future, just please make sure to be specific when asking your question and making sure to tag it appropriately.
Since you've asked how to accomplish this in javascript, here is an answer on how to do this in javascript: https://jsfiddle.net/gqvfx850/24/
The idea is that the items are being added in a to the page in a loop, we then access the last item in the loop:
//if this is the last item in the loop
if(x === listItems.length - 1)
{
And add an event listener for when it's been clicked
//add an event listener that also checks the first box when checking
//this one
radioBtn.addEventListener("click", function()
{
//grab the class of all radio buttons
var firstRadioBtn = document.getElementsByClassName("radio");
//but you only want to change the first one
firstRadioBtn = firstRadioBtn[0];
we then access the first item in the list, and check it
//set the first button to being checked
firstRadioBtn.checked = true;
}, false)
}
This idea works assuming that your HTML is being set up by javascript as well, if that's not the case let me know and I can create another fiddle for you.
I'm not familiar with angular, so if this approach doesn't suit you just please let me know why and I can see if I can provide any other assistance on the matter.
Template
Select Last One <select [(ngModel)]="selectedItem" (ngModelChange)="onSelect($event)">
<option *ngFor="let item of items" [value]="item">{{item}}</option>
</select>
<br><br>
You have selected : {{selectedItem}}
Component
export class AppComponent {
selectedItem:string;
items = [
'Purab', 'Pashchim', 'Uttar', 'Dakshin'
];
onSelect(selectedItem) {
let len = this.items && this.items.length;
this.selectedItem = len && selectedItem === this.items[len-1]?
this.items[0]:selectedItem;
}
}
Demo : https://ng-on-lastselect-checkfirst.stackblitz.io
Source : https://stackblitz.com/edit/ng-on-lastselect-checkfirst

Ng-repeat and directive expand one list item at a time

I have an ng-repeat code with expandable list items. I would like to expand one item at a time.
The way I am trying to do it is
in the html file
<div data-ng-repeat="parts in data track by $index">
<li id="titleHelp" ng-click='setItem($index);">
and in the directive in the setItem function I want to collapse the previously expanded item and expand the new one. Is it possible to access one repeat element in the directive using index?
thanks!
How do you currently expand the list item?
What I would do is set a variable as soon as an item is clicked and in your repeated list do a
<div data-ng-repeat="parts in data track by $index">
<li id="titleHelp" ng-click='setItem($index);">
<div ng-show="$index = selected_item"> <!-- contents --></div>
In your setItem function:
$scope.setItem = function(i) {
$scope.selected_item = i;
}
Declare a object
$scope.obj={selected:null};
After that add a method in the ng repeat,
$scope.isHide = function (id) {
if (id == $scope.obj.selected)
return $scope.obj.selected = "all";
return $scope.obj.selected = id;
}
If you want to hide div, call this method with the id. Do the same thing for the li if you need.

tabs not storing scope vars

My markup:
<li active="active.search">search</li>
<li active="active.lists">lists</li>
<li active="active.find">find</li>
My code:
$scope.active = {
search: true
};
$scope.activate = function(li) {
$scope.active = {};
$scope.active[li] = true;
}
First - the item I've set to be highlighted, doesn't actually get highlighted by default, however they do work when clicked.
Secondly - I'm trying to use what is actually active via:
if ($scope.active[0].search === true) {
... some values
}
Is this not correct? as I can't seem to make this work.
active isn't an angular directive so you need to enclose the variable in angular brackets {{ }} like
active="{{active.search}}"
for angular to parse it.

AngularJS: Hide parent based on number of hidden children

I have the following code:
<div ng-hide="items.length == 0">
<li ng-repeat="item in items" ng-hide="item.hide == true">
{{ item.name }} <hide-button />
</li>
</div>
Imagine that I have 10 items. When I click the button, I set the item.hide to true and the item disappear. When I hide all 10 items, I want to hide the main div. What's the best way to achieve this with AngularJS?
An approach might be to use a function like this:
$scope.checkItems = function () {
var total = $scope.items.length;
for (var i = 0; i < $scope.items.length; ++i) {
if ($scope.items[i].hide) total--;
}
return total === 0;
};
and change the ngHide attribute on the outer div:
ng-hide="checkItems()"
jsfiddle: http://jsfiddle.net/u6vkf6bt/
Another approach is to declare a $scope.allHidden variable and watch over the array like this:
$scope.$watch('items', function (newItems) {
var all = true;
for (var i = 0; i < newItems.length; ++i) {
if (newItems[i].hide !== true) all = false;
}
$scope.allHidden = all;
}, true);
This is checking when anything inside the array is changed, and check if all the hide attributes are set to true.
Then set it on the ngHide attribute:
ng-hide="allHidden"
jsfiddle: http://jsfiddle.net/u6vkf6bt/1/
Among the two, I would choose the first approach, because deep watching may cause performance issues:
This therefore means that watching complex objects will have adverse
memory and performance implications.
From here, under $watch(watchExpression, listener, [objectEquality]); when objectEquality is set to true.
Yet another approach would be updating the counter of hidden items whenever a single item is hidden. Of course this would require the hiding code to be placed within your controller, like this:
$scope.allHidden = false;
// if items can have hide = true property set already from the start
var hiddenItems = $scope.items.filter(function (item) {
return item.hide;
}).length;
// or if they are all initially visible, a straightforward approach:
// var hiddenItems = 0;
$scope.hideItem = function (item) {
item.hide = true;
hiddenItems++;
if (hiddenItems === $scope.items.length) {
$scope.allHidden = true;
}
}
and the hiding would need to be done like this:
<li ng-repeat="item in items" ng-hide="item.hide == true">{{ item.name }}
<button ng-click="hideItem(item)">hide</button>
</li>
jsfiddle: https://jsfiddle.net/723kmyou/1/
Pros for this approach
no need to iterate over the full items array when doing the "should main div be hidden?" check -> possibly better performance
Cons for this approach
you need to place the hideItem function inside your controller
A couple of reasons why I didn't like the other solutions:
Time complexity is always O(n). instead of counting how many items are visible/hidden - a number we do not care about at all in this problem - we should just ask if there's at least one visible. better performance.
Not reusable enough.
My preferred solution is to actually look for the first visible item:
http://plnkr.co/edit/FJv0aRyLrngXJWNw7nJC?p=preview
angular.module('MyApp',[]);
angular.module('MyApp').directive('myParent', function(){
return {
restrict: 'A',
link:function(scope, element){
scope.$watch(function(){
return element.find('>:visible:first').length > 0;
}, function( visible ){
if ( visible ){
element.show();
}else{
element.hide();
}
})
}
}
});
<div my-parent>
<div class="hidden">one child</div>
<div class="hidden">another child</div>
</div>
<div my-parent>
<div class="hidden">third child</div>
<div>another child</div>
</div>
You can even expand this by passing a selector to 'my-parent' attribute to be used in the directive. allows to better specify which items to look for.
I like this approach as it has almost no assumptions.
This can be used for any scenario where children might be hidden. not necessarily ng-repeat.
However the watch might be costly as we're using a :visible selector.
another - assuming ng-repeat is involved - is to watch the value on the scope - http://plnkr.co/edit/ZPVm787tWwx9nbJTNg1A?p=preview
app.directive('myParent', function(){
return{
restrict: 'A',
link: function(scope, element,attrs){
scope.$watch(function(){
try{
value = scope.$eval(attrs.myParent);
var i = value.length-1;
for ( ; i >= 0; i-- ){
if ( value[i].hide != false ){
return true;
}
}
}catch(e){
return false
}
return false;
}, function( newValue ){
if ( !!newValue ) { element.show(); } else { element.hide();}
})
}
}});
<ul my-parent="awesomeThings">
<li ng-repeat="thing in awesomeThings">{{thing.value}}</li>
</ul>
The watch here might be less costly, not sure, but it doesn't handle the dom, so there's a good chance for that..
While this is very similar to other solutions, it is implemented as a highly reusable directive, and looks for the first visible item.
In my solutions I also assume jquery is available.
There are many other ways to implement this if required:
AngularJS: element.show() in directive not working
regarding watch performance, not sure which way is better. I am using a function to evaluate the watched value and I am not sure if this is the best way to handle it.

Can I apply a css class based on the $index and the length of an ng-repeat object?

I hope this hasn't been answered anywhere else. I am trying to create a rule with ng-class that:
displays firstTagClass if the item is $first in the repeater index
is not displayed when the item is the only item in the repeater
that doesn't use scope.objects.length (because scope.objects is not an array but an object in this specific case).
USE CASE
Can be a great starting point to crop avatar pictures and hold them in the same container.
SO FAR...
I completed the first and third objectives but I am stuck with the second. I tried to add a second condition !$last but it doesn't work.
<div
ng-repeat="object in objects"
ng-if="$index < 4 && object.id !== me.id"
ng-class="{firstTagClass: $first && !$last}">
</div>
Is there a way to achieve all of my goals through ng-class or should I build a custom directive?
Thanks in advance for your help.
Hadrien
UPDATE
I am adding a Codepen: http://codepen.io/anon/pen/ZYaBjW
I found an answer making use of a directive. You can check the solution by removing the 3rd object in $scope.objects here: http://codepen.io/anon/pen/LEOopR
HTML
I added the block-style directive and used the class attribute instead of ng-class since I chose to use element.removeClass() in my link function. It seemed more logical to remove a class because my intention was to make it the exception and not the rule.
<div ng-repeat="object in objects"
class="firstTagClass"
ng-if="$index < 4 && object.id !== me.id"
position="{{index}}"
block-style>
{{object.id}}
</div>
JS
The details of the blockStyle directive:
restrict: 'A',
link: function(scope, el, attrs){
var objects = scope.objects;
var me = scope.me;
var userCount = Object.keys(objects).length;
var position = parseInt(attrs.position);
//assign hasMe attribute for objects collection with me object
if(objects){
for(var prop in objects){
if(objects[prop].id === me.id){
objects.hasMe = true;
}
}
}
//handle particular case of 1 object displayed
if(userCount < 2 || userCount < 3 && objects.hasMe === true){
el.removeClass('firstTagClass');
}else if(position > 0){
el.removeClass('firstTagClass');
}
} // end of link function

Categories

Resources