AngularJs styling a single clicked element within an ng-repeat - javascript

I have a collapsible accordion using ng-repeat to render its options. On the right there is a downward facing glyphicon. This glyphicon should become upward facing once the job is clicked and expanded.
Update: Using Amine's response, the clicked header gets a glyphicon-down as it should be. But when collapsed and clicked outside, the glyphicon stays downward as the index is set and actually needs to become upward facing again when collapsed. How can I set $index to undefined again when the item is clicked again?
My code:
<div class="col span_2_of_3">
<div ng-repeat="item in jobs">
<accordion>
<accordion-group ng-click="state.selected = $index">
<accordion-heading>
<h2>
"{{item.title}}"
<i class="pull-right glyphicon "
ng-class="{'glyphicon-chevron-up': $index == state.selected,
'glyphicon-chevron-down': $index != state.selected }"
ng-click="state.selected = $index"
></i>
</h2>
</accordion-heading>
{{item.description}}
</accordion-group>
</accordion>
</div>
</div>
In my controller I have:
$scope.state = { selected: undefined };

You're not removing the glyphicon-chevron-down class when you click on the element. You end up with an element that has both classes ; whatever CSS rule comes last will be applied.
Try this :
<i class="pull-right glyphicon "
ng-class="{'glyphicon-chevron-up': $index == state.selected,
'glyphicon-chevron-down': $index != state.selected }"></i>
Here is a fiddle showing a solution (note that I have removed the ng-click binding on the icon as that's already handled by the accordion-group).

Related

make a parent div clickable except for a child icon

<div
[ngClass]="{ 'template-row': !isExpandable, 'folder-row': isExpandable }"
class="label-with-icons clickable"
(click)="expanderClicked()"
(click)="itemSelected()"
>
<i *ngIf="isExpandable" [ngClass]="getArrowIconClass()"></i><i [ngClass]="getIconClass()"></i>
<span *ngIf="id" class="name ">{{ name }}</span>
<span *ngIf="!id" class="name">{{ name }}</span>
<i *ngIf="!isExpandable" class="io-icon-trash clickable" id="delete" (click)="deleteTemplate()"></i>
</div>
I want the entire div to be clickable but not the icon with id= delete. I want to have a different click event on that
I dont use jquery , i work with TS
You can stop the event to propagate up in the DOM tree with event.stopPropagation() :
(click)="deleteTemplate($event)"></i>
deleteTemplate(e: Event) {
e.stopPropagation(); // <----event won't travel up
}

AngularJS ng-click multiple calls

I wrote the next code, but when I click on the trash icon, the ng-click of this element is thrown, but the ng-click of the div container is thrown too, I don't need the second one, just the first call, could some body help me.
<div ng-if="order.selectedProducts === null || order.selectedProducts.length > 0"
class="cartCol hoverable" ng-repeat="product in order.selectedProducts track by $index"
ng-click="showProductDetailed(product)">
<div class="cartHeading" ng-bind="product.name"></div>
<a href="" class="trashIcon" ng-click="removeSelectedProduct(product);">
<i class="fa fa-trash"></i>
</a>
<div class="cartSizeInfo">
<span class="fltLft">{{product.productTypeName}}</span>
<span class="fltRht">Bs. {{product.price}}</span>
</div>
</div>
I had to put $event.stopPropagation(); after the first call.

I want to skip the item in ng-repeat using ng-if

I am trying to use ng-if in ng-repeat for implementing Accordions. Based upon the condition value, the ng-repeat should skip some items in ng-repeat.
E.g. If the item.condition is true then only it should display accordion.
The code below is what I have so far and it is not working. Does it look right?
<accordion close-others="true">
<accordion-group is-open="isopen" ng-repeat="item in items | limitTo:2" ng-if="item.condition == "true"",ng-init="isopen=2">
<accordion-heading>
{{item.label}}
<i class="pull-right glyphicon"
ng-class="{'icon-arrow-up': isopen, 'icon-arrow-down': !isopen}"></i>
</accordion-heading>
</accordion-group>
</accordion>
Your ng-if contain double extra quotes, It should be ng-if="item.condition == true", Also remove the , from the accordion element
Also you could minimize your condition to ng-if="item.condition" so then expression will return true and false on item.condition variable evaluation.
Markup
<accordion close-others="true">
<accordion-group is-open="isopen" ng-repeat="item in items | limitTo:2"
ng-if="item.condition" ng-init="isopen=2">
<accordion-heading>
{{item.label}}
<i class="pull-right glyphicon" ng-class="{'icon-arrow-up': isopen, 'icon-arrow-down': !isopen}"></i>
</accordion-heading>
</accordion-group>
</accordion>

Ember navbar UI condition based on currentPath

I must not be doing something right. I have the following:
application.hbs
{{#view App.NavbarView}}{{/view}}
{{outlet}}
with the following template for Navbar
_navbar.hbs
<div class="toolbar">
<div class="row">
<div class="absolute top-left">
<button {{action "back"}} class="btn passive back"><i class="fa fa-play"></i></button>
</div>
{{#if hasTabs}}
<div class="small-centered columns">
<div class="tabs">
<ul>
{{#link-to 'stories' tagName="li" class="tab"}}<i class="fa fa-book"></i> Stories{{/link-to}}
{{#link-to 'mylevels' tagName="li" class="tab"}}<i class="fa fa-user"></i> My Levels{{/link-to}}
{{#link-to 'arcade.index' tagName="li" class="tab"}}<i class="fa fa-gamepad"></i> Arcade{{/link-to}}
</ul>
</div>
</div>
{{else}}
<div class="small-6 small-offset-3 columns">
<h2 class="title">{{ pageTitle App.currentPath }}</h2>
</div>
{{/if}}
{{#if currentUser.userName}}
<div class="absolute top-right">
<span class="user-hello">Welcome Back, <strong>{{ currentUser.userName }}</strong></span>
<button {{action "transitionAccount" currentUser._id}} class="square logged-in"><i class="fa fa-user"></i></button>
</div>
{{ else }}
<div class="absolute top-right">
<button {{action "transitionLogin"}} class="square logged-out"><i class="fa fa-user"></i></button>
</div>
{{/if}}
</div>
</div>
So all it is is a typical fixed navbar and in the middle of it I display what page you are on, if you happen to be on a page that has tabbed content, I show a tab container instead.
So I'm just using this.get('currentPath') in my App controller and comparing it against a group of route names to trigger true/false (I need an observer so it looks at the route change since the Navbar is in inline view at the Application level).
app.js
App.ApplicationController = Ember.ObjectController.extend({
updateCurrentPath: function() {
App.set('currentPath', this.get('currentPath'));
}.observes('currentPath'),
tabs: function() {
var route = this.get('currentPath'),
group = ['arcade.index', 'mylevels', 'stories', 'arcade', 'arcade.loading'];
console.log("ROUTE: ", route);
var tabs = group.indexOf(route) > -1 ? true : false;
return tabs;
}.observes('currentPath'),
// no idea what to do here
hasTabs: function() {
this.tabs();
}.property('tabs')
});
So, basically, no matter what, the tab UI is showing up, but I only want it to show up if that tabs observer is true. With some debugging I'm getting all the console output I would expect but I tried just doing {{#if tabs}} (just using the observer directly) and that always fires true (always shows the tabs UI). I assumed that's because it was an observer and not an actual controller property I could use in my template, so I tried just setting the hasTabs property and referencing the observer, but that doesn't seem to work. I realize I am fundamentally not understanding how this should work. Any thoughts?
If I understand your question correctly you should be able to just change your code to this (renamed tabs to hasTabs, removed previous hasTabs function. Changed from observes currentPath to be property of current path, removed the tabs variable assignment and replaced with the return, reduced the boolean conditional to the simple comparison operator). This is what I'd do, anyway. :) H2H
hasTabs: function() {
var route = this.get('currentPath'),
group = ['arcade.index', 'mylevels', 'stories', 'arcade', 'arcade.loading'];
return group.indexOf(route) > -1;
}.property('currentPath')

Handle open/collapse events of Accordion in Angular

If I have this code:
<accordion-group heading="{{group.title}}" ng-repeat="group in groups">
{{group.content}}
</accordion-group>
Using AngularJS, angular-ui and Twitter Bootstrap, is it possible to make the accordion call some action when opened? I know I can't simply add ng-click, because that is already used after it's "compiled" to HTML for opening/collapsing of the group.
Accordion groups also allow for an accordion-heading directive instead of providing it as an attribute. You can use that and then wrap your header in another tag with an ng-click.
<accordion-group ng-repeat="group in groups" heading="{{group.title}}" is-open="group.open">
<accordion-heading>
<span ng-click="opened(group, $index)">{{group.content}}</span>
</accordion-heading>
</accordion-group>
Example: http://plnkr.co/edit/B3LC1X?p=preview
Here's a solution based on pkozlowski.opensource solution.
Instead of adding a $watch on each item of the collection, you can use a dynamically defined Property. Here, you can bind the IsOpened property of the group to the is-open attribute.
<accordion-group ng-repeat="group in groups" heading="{{group.title}}" is-open="group.IsOpened">
{{group.content}}
</accordion-group>
So, you can dynamically add the IsOpened property on each item of the collection in the controller :
$scope.groups.forEach(function(item) {
var isOpened = false;
Object.defineProperty(item, "IsOpened", {
get: function() {
return isOpened;
},
set: function(newValue) {
isOpened = newValue;
if (isOpened) {
console.log(item); // do something...
}
}
});
});
Using properties instead of watches is better for performances.
There is the is-open attribute on the accordion-group which points to a bindable expression. You could watch this expression and execute some logic when a given accordion group is open. Using this technique you would change your markup to:
<accordion-group ng-repeat="group in groups" heading="{{group.title}}" is-open="group.open">
{{group.content}}
</accordion-group>
so that you can, in the controller, prepare a desired watch expression:
$scope.$watch('groups[0].open', function(isOpen){
if (isOpen) {
console.log('First group was opened');
}
});
While the above works it might be a bit cumbersome to use in practice so if you feel like this could be improved open an issue in https://github.com/angular-ui/bootstrap
Here's a solution inspired by kjv's answer, which easily tracks which accordion element is open. I found difficult getting ng-click to work on the accordion heading, though surrounding the element in a <span> tag and adding the ng-click to that worked fine.
Another problem I encountered was, although the accordion elements were added to the page programmatically, the content was not. When I tried loading the content using Angular directives(ie. {{path}}) linked to a $scope variable I would be hit with undefined, hence the use of the bellow method which populates the accordion content using the ID div embedded within.
Controller:
//initialise the open state to false
$scope.routeDescriptors[index].openState == false
function opened(index)
{
//we need to track what state the accordion is in
if ($scope.routeDescriptors[index].openState == true){ //close an accordion
$scope.routeDescriptors[index].openState == false
} else { //open an accordion
//if the user clicks on another accordion element
//then the open element will be closed, so this will handle it
if (typeof $scope.previousAccordionIndex !== 'undefined') {
$scope.routeDescriptors[$scope.previousAccordionIndex].openState = false;
}
$scope.previousAccordionIndex = index;
$scope.routeDescriptors[index].openState = true;
}
function populateDiv(id)
{
for (var x = 0; x < $scope.routeDescriptors.length; x++)
{
$("#_x" + x).html($scope.routeDescriptors[x]);
}
}
HTML:
<div ng-hide="hideDescriptions" class="ng-hide" id="accordionrouteinfo" ng-click="populateDiv()">
<accordion>
<accordion-group ng-repeat="path in routeDescriptors track by $index">
<accordion-heading>
<span ng-click="opened($index)">route {{$index}}</span>
</accordion-heading>
<!-- Notice these divs are given an ID which corresponds to it's index-->
<div id="_x{{$index}}"></div>
</accordion-group>
</accordion>
</div>
I used an associative array to create a relationship between the opened state and the model object.
The HTML is:
<div ng-controller="CaseController as controller">
<accordion close-others="controller.model.closeOthers">
<accordion-group ng-repeat="topic in controller.model.topics track by topic.id" is-open="controller.model.opened[topic.id]">
<accordion-heading>
<h4 class="panel-title clearfix" ng-click="controller.expand(topic)">
<span class="pull-left">{{topic.title}}</span>
<span class="pull-right">Updated: {{topic.updatedDate}}</span>
</h4>
</accordion-heading>
<div class="panel-body">
<div class="btn-group margin-top-10">
<button type="button" class="btn btn-default" ng-click="controller.createComment(topic)">Add Comment<i class="fa fa-plus"></i></button>
</div>
<div class="btn-group margin-top-10">
<button type="button" class="btn btn-default" ng-click="controller.editTopic(topic)">Edit Topic<i class="fa fa-pencil-square-o"></i></button>
</div>
<h4>Topic Description</h4>
<p><strong>{{topic.description}}</strong></p>
<ul class="list-group">
<li class="list-group-item" ng-repeat="comment in topic.comments track by comment.id">
<h5>Comment by: {{comment.author}}<span class="pull-right">Updated: <span class="commentDate">{{comment.updatedDate}}</span> | <span class="commentTime">{{comment.updatedTime}}</span></span></h5>
<p>{{comment.comment}}</p>
<div class="btn-group">
<button type="button" class="btn btn-default btn-xs" ng-click="controller.editComment(topic, comment)">Edit <i class="fa fa-pencil-square-o"></i></button>
<button type="button" class="btn btn-default btn-xs" ng-click="controller.deleteComment(comment)">Delete <i class="fa fa-trash-o"></i></button>
</div>
</li>
</ul>
</div>
</accordion-group>
</accordion>
The controller snippet is:
self.model = {
closeOthers : false,
opened : new Array(),
topics : undefined
};
The 'topics' are populated on an AJAX call. Separating the 'opened' state from the model objects that are updated from the server means the state is preserved across refreshes.
I also declare the controller with ng-controller="CaseController as controller"
accordion-controller.js
MyApp.Controllers
.controller('AccordionCtrl', ['$scope', function ($scope) {
$scope.groups = [
{
title: "Dynamic Group Header - 1",
content: "Dynamic Group Body - 1",
open: false
},
{
title: "Dynamic Group Header - 2",
content: "Dynamic Group Body - 2",
open: false
},
{
title: "Dynamic Group Header - 3",
content: "Dynamic Group Body - 3",
open: false
}
];
/**
* Open panel method
* #param idx {Number} - Array index
*/
$scope.openPanel = function (idx) {
if (!$scope.groups[idx].open) {
console.log("Opened group with idx: " + idx);
$scope.groups[idx].open = true;
}
};
/**
* Close panel method
* #param idx {Number} - Array index
*/
$scope.closePanel = function (idx) {
if ($scope.groups[idx].open) {
console.log("Closed group with idx: " + idx);
$scope.groups[idx].open = false;
}
};
}]);
index.html
<div ng-controller="AccordionCtrl">
<accordion>
<accordion-group ng-repeat="group in groups" is-open="group.open">
<button ng-click="closePanel($index)">Close me</button>
{{group.content}}
</accordion-group>
<button ng-click="openPanel(0)">Set 1</button>
<button ng-click="openPanel(1)">Set 2</button>
<button ng-click="openPanel(2)">Set 3</button>
</accordion>
</div>
You can do it w/ an Angular directive:
html
<div uib-accordion-group is-open="property.display_detail" ng-repeat="property in properties">
<div uib-accordion-heading ng-click="property.display_detail = ! property.display_detail">
some heading text
</div>
<!-- here is the accordion body -->
<div ng-init="i=$index"> <!-- I keep track of the index of ng-repeat -->
<!-- and I call a custom directive -->
<mydirective mydirective_model="properties" mydirective_index="{% verbatim ng %}{{ i }}{% endverbatim ng %}">
here is the body
</mydirective>
</div>
</div>
js
app.directive("mydirective", function() {
return {
restrict: "EAC",
link: function(scope, element, attrs) {
/* note that ng converts everything to camelCase */
var model = attrs["mydirectiveModel"];
var index = attrs["mydirectiveIndex"];
var watched_name = model + "[" + index + "].display_detail"
scope.$watch(watched_name, function(is_displayed) {
if (is_displayed) {
alert("you opened something");
}
else {
alert("you closed something");
}
});
}
}
});
There are some idiosyncrasies about my setup there (I use Django, hence the "{% verbatim %}" tags), but the method should work.

Categories

Resources