Applying css class depending on subelements - javascript

I want to set css classes to items of a list depending of subelements matches a certain criterion or not. The structure is like in the following example:
<ul ng-controller="Navigation">
<li>Category A
<ul>
<li>a1</li>
<li>a2</li>
</ul>
</li>
<li>Category B
<ul>
<li>b1</li>
<li>b2</li>
</ul>
</li>
<li>contact</li>
</ul>
My model is the current page, say a2.html. If a link has the same href attribute as the model value, it should have a certain css class (active). This could be done with this expression:
<a href="a1.html" ng-class="{'active': currentPage == 'a1.html'}>
But this is a bit inelegant, because I have to repeat the file name (a1.html). Would it be possible to pass the current element to a function? Something like this: ng-class="getClass(currentElement)"
The next problem is, that I want to select parent elements depending on whether a child has the class active or not. If a1 in my above example is selected, then Category A should get the class active too.
Conclusion
'stewie's solution works, but I came to the conclusion that Angular is not the best tool for this job. It is not a web 'application' (the domain of Angular) but static html which should be enriched a bit.
This simple jQuery snippet does the job:
var activeLink = $("a").filter(function() {
return $(this).attr("href") == currentPage();
});
activeLink.addClass("active");
activeLink.parents("li").children("a").addClass("active");

It can be done by using a custom directive on your UL element, which would traverse the list whenever the model is changed and set the appropriate 'active' class on matching items. See this Plunker as an example. Please note that the directive can be further optimized. It's only a demonstration.
HTML:
<ul menu ng-controller="Navigation">
<li>Category A
<ul>
<li>a1</li>
<li>a2</li>
</ul>
</li>
<li>Category B
<ul>
<li>b1</li>
<li>b2</li>
</ul>
</li>
<li>contact</li>
</ul>
JS:
var app = angular.module('plunker', []);
app.controller('Navigation',
function($scope) {}
);
app.directive('menu',
function(){
return {
link: function ($scope, $element) {
var link, li;
$scope.$watch('currentPage', function(page){
activate(page);
});
function activate(page){
angular.forEach($element.find('li'), function(elm){
li = angular.element(elm);
link = li.find('a');
if(link.attr('href') === $scope.currentPage){
li.addClass('active');
li.parents('li').addClass('active');
return;
}
li.removeClass('active');
});
}
}
};
}
);

I had a look at #Stewei 's the solution for this. But writing up the solution using a directive for this just did not make sense to me. I think that the directives are only meant to be written when you are creating a component or perhaps when you are stamping out templates. Even for stamping out templates I would suggest people to use ng-include or something like unless you want the semantics that a custom element provides.
But, I believe the Angular way of doing this is to totally separate out the behavioral logic from the 'App specific' logic. I would argue that you write directives in such a way that they are independent of the controller around it.
Here is a plunkr that actually solves this, hopefully in the most "Angular way" possible. This can be further decoupled, if you could use some kind of a data model for all the link's and could possibly ng-repeat over that model. But that is a choice upto the developer. Since you mentioned that it is a static page, this solution probably suits it best.
http://plnkr.co/edit/DjaYi1ofpFKgKfhtakub?p=preview
In your original question, you did ask if you could pass a currentElement to the getClass method. I have asked this before and the answer is NO. The only place where you could pass a reference to the currentElement is in a directive controller, but that is currently out of topic.
Here is the code that achieves the task at hand.
HTML :
<div ng-controller="NavController">
<ul >
<li ng-class="getCategoryClass('A')">
Category A
<ul>
<li ng-class="getClass('a1.html','A')">a1</li>
<li ng-class="getClass('a2.html','A')">a2</li>
</ul>
</li>
<li ng-class="getCategoryClass('B')">
Category B
<ul>
<li ng-class="getClass('b1.html','B')">b1</li>
<li ng-class="getClass('b2.html','B')">b2</li>
</ul>
</li>
<li ng-class="getClass('contact.html','contact')">contact</li>
</ul>
<input ng-model="currentPage" />
<br>
currentPage: {{currentPage}}
</div>
JS :
app.controller('NavController',
function($scope) {
$scope.currentPage = "";
$scope.currentCategory = "";
$scope.pageActive = false;
$scope.getClass = function(page,category){
if( page === $scope.currentPage ) {
$scope.pageActive = true;
$scope.currentCategory = category;
return "active";
}
else{
return "";
}
};
$scope.$watch('currentPage',function(val){
$scope.pageActive = false;
});
$scope.onLinkClick = function($event){
$event.preventDefault();
};
$scope.getCategoryClass = function(category){
return $scope.currentCategory === category && $scope.pageActive ? "active" : "";
};
}
);

Related

Active class nav item based on retrieved url portion

I would like to add a css class (.active) on the appropriate navigation link.
My navigation:
<nav>
<ul>
<li id="Thuispagina">Thuispagina</li>
<li id="Nieuws">Nieuws</li>
<li id="Skirms">Skirms</li>
<li id="Reservatie">Reservatie</li>
<li id="Fotogalerij">Fotogalerij</li>
<li id="Contact">Contact</li>
<li id="Forum">Forum</li>
</ul>
</nav>
If I would be on one of the following pages: domain.com/airsoft-contact/index.php, domain.com/airsoft-contact/edit.php or domain.com/airsoft-contact/delete.php it should add the active class to the li item with id Contact
If I would be on one of the following pages: domain.com/airsoft-fotogalerij/index.php, domain.com/airsoft-fotogalerij/edit.php or domain.com/airsoft-fotogalerij/delete.php it should add the active class to the li item with id Fotogalerij
On the other hand there is one exception: the index.php page is not in any submap so the logic should make an exception there.
Well the better or simple way is using jquery but u can do it with php too using a condition example class="<?php if ($_SERVER['PHP_SELF']=="ur script name"){ echo "active";}?>" or u can use ternary operator for better views so this is a way, u can find others
The easiest way to implement this (if you aren't an expert user) is to add in the navigation element a js like this for every link
if(window.location.href == "yourlink"){
document.getElementById("elementid").class = "active";
}
Another way can be something like:
var links = [ new Link("yoururl1", "yourelement1"), new Link("yoururl2", "yourelement2"), new Link("yoururl3", "yourelement3")];
for(link in links){
if(link.url == window.location.href){
document.getElementById(link.elementid).class = "active";
}
}
function Link (url, elementid){
this.url = url;
this.elementid = elementid;
}

Add a Class to specific list item based on attribute value angularJs

Here is my code that i am struggling with:
.directive('mydirective', function() {
return {
template: '<ul>\
<li priority="500"><i></i>Inbox</li>\
<li priority="500"><i></i>Language</li>\
<li priority="1000"><i></i>Settings</li>\
<li priority="500"><i></i>Contact</li>\
<li priority="1000"><i></i>Help</li>\
</ul>',
link: function(v, e, a){
var elm = e[0].childNodes[0].children;
}
}
});
All i want to do is to add a class to list items that have let say the priority= 1000. I am able to get the element but when i do the loop i don't know how to filter the attribute so then i can add the class. Anyone came around this?
Thanks
If you are going to be sending data and using a ng-repeat then you should be able to use
ng-class="{'className': data.priority === 1000}"

I need jQuery to perform a task if two conditions are true

I need some help to put an if statement together using jQuery. I want to change the logo on my site, if two conditions are true.
Here is some pseudo code, hopefully explaining what i want to archive:
if(li hasClass active and data-menuid is equal to 0033){
change logo...
}
Here is a simple example of the menu structure:
<div id="menu">
<ul id="menu-primary">
<li class="menuactive" data-menuid="0011">
Test1
<ul class="menu-dropdown">
<li data-menuid="0022">Test2</li>
<li class="active" data-menuid="0033">Test3</li>
</ul>
</li>
<li data-menuid="0044">Test4</li>
<li data-menuid="0055">Test5</li>
</ul>
</div>
You can check the combination of class and Attribute Equals Selector [name="value"]
if($('li.menuactive[data-menuid="0033"]').length){
//Your code to change the logo
}
You can use $.fn.filter()
Reduce the set of matched elements to those that match the selector or pass the function's test.
var listMeetingCondition = $('li').filter(function(){
return $(this).hasClass('menuactive') && $(this).attr('data-menuid') == "0033"
});
if(listMeetingCondition.length){
//Your code to change the logo
}
if($('li:has(.menuactive[data-menuid="0033"])').length){
change logo...
}
Another workaround:
var $target = $('li', '#menu-primary');
if( $target.hasclass('active') && $target.data('menuid') == '0033' ){
// change logo
}

Toggle an edit state in AngularJS and change items's ng-click to another function

I got an edit button and a list containing items. When I click edit I want to go into "edit mode" and when in edit mode I want to be able to click on items in the list and remove them on click. When not in edit mode these items calls another function so I'm not sure how to achieve this.
Here's my markup:
<span class="side-text"><a ng-click="toggleEditState()">Edit</a></span>
<ul id="fav-tags-list" class="list">
<li ng-repeat="tag in tagsList" ng-bind="tag"
ng-click="shared.getImages(tag)"></li> //If editState = true then change to removeTag(tag)
</ul>
And the controller which handles the edit state mode:
flickrApp.controller('tagsCtrl', ['$scope', '$firebase', 'shared', function($scope, $firebase, shared) {
$scope.editState = false;
$scope.shared = shared;
$scope.removeTag = function(tag) {
shared.updateTags(tag);
var newTagsList = shared.getTags();
sync.$set({favoriteTags: newTagsList});
}
$scope.toggleEditState = function() {
if ($scope.editState === false) {
$scope.editState = true;
}
else {
$scope.editState = false;
}
}
}]);
How would I achieve what I want here? If there's a smarter solution I'll take it.
I think there is a very short and elegant solution for this:
<div ng-click="select? a() : b()">some element</div>
If select is true then a() is called, otherwise b() is called.
Here is a plnkr showing my solution on a couple of divs, implementing it for your use case shouldn't be hard.
You can create a function that calls either shared.removeTag(tag) or someOtherFunction(tag) depending on $scope.editState variable.
For example:
$scope.myFunction = function(tag) {
if ($scope.editState) {
shared.removeTag(tag)
} else {
shared.getImages(tag)
}
}
HTML:
<li ng-repeat="tag in tagsList" ng-bind="tag"
ng-click="myFunction(tag)"></li>
One possible solution is to have two lists. One that displays when not in edit mode and the other that does.
<span class="side-text"><a ng-click="toggleEditState()">Edit</a></span>
<ul id="fav-tags-list" class="list" ng-show="!editState">
<li ng-repeat="tag in tagsList" ng-bind="tag"
ng-click="shared.getImages(tag)"></li>
</ul>
<ul id="fav-tags-list" class="list" ng-show="editState">
<li ng-repeat="tag in tagsList" ng-bind="tag"
ng-click="shared.removeTag(tag)"></li>
</ul>

How do I move a view around the DOM with angular.js?

How can I move an element to different places in the DOM with angular js?
I have a list of elements like so
<ul id="list" ng-controller="ListController">
<li ng-controller="ItemController"><div>content</div></li>
<li ng-controller="ItemController"><div>content</div></li>
<li ng-controller="ItemController"><div>content</div></li>
<li ng-controller="ItemController">
<div>content</div>
<div id="overlay"></div>
</li>
</ul>
What I'm trying to accomplish is moving the #overlay from place to place within the list without having to have a hidden duplicate in every item that I flag hidden/unhidden.
If this was jquery I could just do something like this:
$("#overlay").appendTo("#list li:first-child");
Is there an equivalent way to do this in angular?
Thanks to your clarifications I can understand that you've got a list of items. You would like to be able to select one item in this list (swipe but potentially other events as well) and then display an additional DOM element (div) for a selected item. If the other item was selected it should be un-selected - this way only one item should have an additional div displayed.
If the above understanding is correct, then you could solve this with the simple ng-repeat and ng-show directives like this:
<ul ng-controller="ListController">
<li ng-repeat="item in items">
<div ng-click="open(item)">{{item.content}}</div>
<div ng-show="isOpen(item)">overlay: tweet, share, pin</div>
</li>
</ul>
where the code in the controller would be (showing a fragment of it only):
$scope.open = function(item){
if ($scope.isOpen(item)){
$scope.opened = undefined;
} else {
$scope.opened = item;
}
};
$scope.isOpen = function(item){
return $scope.opened === item;
};
Here is the complete jsFiddle: http://jsfiddle.net/pkozlowski_opensource/65Cxv/7/
If you are concerned about having too many DOM elements you could achieve the same using ng-switch directive:
<ul ng-controller="ListController">
<li ng-repeat="item in items">
<div ng-click="open(item)">{{item.content}}</div>
<ng-switch on="isOpen(item)">
<div ng-switch-when="true">overlay: tweet, share, pin</div>
</ng-switch>
</li>
</ul>
Here is the jsFiddle: http://jsfiddle.net/pkozlowski_opensource/bBtH3/2/
As an exercise for the reader (me), I wanted to try a custom directive to accomplish this. Here is what I came up with (after many failed attempts):
<ul ng-controller="ListController">
<li ng-repeat="item in items">
<div singleton-overlay>{{item.content}}</div>
</li>
</ul>
A service is required to store the element that currently has the overlay, if any. (I decided against using the controller for this, since I think a 'service + directive' would make for a more reusable component than a 'controller + directive'.)
service('singletonOverlayService', function() {
this.overlayElement = undefined;
})
And the directive:
directive('singletonOverlay', function(singletonOverlayService) {
return {
link: function(scope, element, attrs) {
element.bind('click', moveOrToggleOverlay);
function moveOrToggleOverlay() {
if (singletonOverlayService.overlayElement === element) {
angular.element(element.children()).remove();
singletonOverlayService.overlayElement = undefined;
} else {
if (singletonOverlayService.overlayElement != undefined) {
// this is a bit odd... modifying DOM elsewhere
angular.element(singletonOverlayService.overlayElement.children()).remove();
}
element.append('<div>overlay: tweet, share, pin</div>')
singletonOverlayService.overlayElement = element;
jsFiddle: http://jsfiddle.net/mrajcok/ya4De/
I think the implementation is a bit unconventional, though... the directive not only modifies the DOM associated with its own element, but it may also modify the DOM associated with the element that currently has the overlay.
I tried setting up $watches on scope and having the singleton store and modify scope objects, but I couldn't get the $watches to fire when I changed the scope from inside the moveOrToggleOverlay function.

Categories

Resources