How do I use ng-class without ng-repeat? - angularjs

I am relatively new to AngularJS and am loving every moment of it! I have an unordered list of products. When the user hovers over them, I need the list element to have an active class set on them. I am currently doing it this way:
<ul>
<li ng-repeat="product in products" ng-mouseover="showDetails(product, $event)" ng-class="{active: current == product}">{{product}}</li>
</ul>
And my showDetails is as follows:
function showDetails(product, $event) {
$scope.current = product;
...some other logic...
}
Now, everything is working just fine, however, I was wondering if it was possible to set the class without the ng-repeat and having no product variable to bind to?
<ul>
<li ng-mouseover="showDetails($event)" ng-class="{<i don't know what to put here> }">foo</li>
<li ng-mouseover="showDetails($event)" ng-class="{<i don't know what to put here> }">bar</li>
<li ng-mouseover="showDetails($event)" ng-class="{<i don't know what to put here> }">A</li>
<li ng-mouseover="showDetails($event)" ng-class="{<i don't know what to put here> }">B</li>
</ul>
How should I write my showDetails function to set the class this time? My first try is:
function showDetails($event) {
var text = $event.target.textContent;
$scope.current = text
}
But what do I do in the ng-class attribute?

If a pure CSS solution is not possible then you can create a directive and toggle a CSS class using JQuery within that directive. Apply the directive on the ul as an attribute or class. In that directive you can do
iElement.find("li").hover(...

Related

List items not dragging using Sortable.js

Sortable.Js newbie here, so please be gentle.
I'm trying to implement a sortable list in my page using Sortable.js. I'm not getting any errors upon execution, however when I tried to drag my list items, they're not moving at all.
Here's my HTML:
<ul id="forcedranking" class="wizard-contents-container-ul forcedRankCls" ng-show="isForcedRankingQuestion">
<div class="answers-container">
<div class="answer-child-container">
<li ng-repeat="query in currentQuestionObject.choices | orderBy:['sequence','answer']" class="listModulePopup forcedRankingChoice" data-index="{{$index}}" ng-model="query.value">
{{query.answer}}
</li>
</div>
</div>
</ul>
And here's my JS:
/* Get current input type for Clear All checkbox activation */
$scope.currentInputType = $scope.currentQuestionObject.inputType.toLowerCase();
if ($scope.currentInputType == "forced ranking") {
$scope.isForcedRankingQuestion = true;
/* SORTABLE FOR FORCED RANKING */
var mySort = document.getElementById("forcedranking");
// forcedranking is my id for the <ul>
var sortable = Sortable.create(forcedranking, {});
// Tried setting this because I thought this was the culprit. Turns out it's not.
sortable.option("disabled", false);
I called my Sortable.js like so (underneath my angularJS libraries):
<script type="text/javascript" src="content1/carousel/Sortable.js"></script>
Overall, I think Sortable's a really neat library, which is why I want to make it work so bad. But I just don't know what I'm doing wrong here.
The problem is you are not following the sortable angular documentation. They recently change the project and separated the js from the frameworks.
So first you need to include the lib and the angular sortable lib angular-legacy-sortablejs
Sortable.js
ng-sortable.js
Second inject the module 'ng-sortable' in your app
Then you can pass the options (if you need to) in the html via directive or use it in the controller
HTML:
<ul ng-sortable="{group: 'foobar'}">
<li ng-repeat="query in currentQuestionObject.choices">{{query.answer}}</li>
</ul>
Or you can pass an object declared in your controller with the options, ex:
$scope.sortableConfig = {
group: 'collection',
animation: 150,
handle: '.handle',
filter: '.inbox'
}
<ul ng-sortable="sortableConfig">
<li ng-repeat="collection in test">
<div class="handle"></div>
{{collection.name}}
</li>
</ul>
Hope this help you!
I think what's going on here is that your JS is being executed before Angular is done rendering all the LI items, but it's hard to tell from your snippet of JS.
Just for testing, see if the items become draggable after 1 second if you change these two lines:
/* SORTABLE FOR FORCED RANKING */
var mySort = document.getElementById("forcedranking");
// forcedranking is my id for the <ul>
var sortable = Sortable.create(forcedranking, {});
into this:
window.setTimeout(function() {
/* SORTABLE FOR FORCED RANKING */
var mySort = document.getElementById("forcedranking");
// forcedranking is my id for the <ul>
var sortable = Sortable.create(forcedranking, {});
}, 1000);
Also, I don't believe that <div> elements are valid children for <ul> elements, so that may also be your problem. If the Javascript change does nothing, then perhaps you should try to change your html so that your <li> items are direct children of the <ul> element.
Hope this helps.

Pagination repeater has an active class that i must move when pagination button clicked

Take the following pagination html and repeater:
<ul id="ProductListPagination" class="pagination">
<li class="disabled"><span aria-hidden="true">«</span><span class="sr-only">Previous</span></li>
<li ng-repeat="n in PageCount" ng-class="{active: n==1}"><a ng-click="Paginate( n )" href="#"><% n %> <span class="sr-only">(current)</span></a></li>
</ul>
How do I go about moving the active class on the repeater when one of the pagination buttons are pressed... Is there a built in angular way?...
context:
If there is no built in way in angular, how do I pass the dom element through to the Paginate( n ) function?
I have the receiving function:
$scope.Paginate = function( obj, page ){
// Remove currently active button's active class
$( "#ProductListPagination li.active" ).each( function() {
$( this ).removeClass( "active" );
} );
// Add to element just clicked on
$( obj ).parent().addClass( "active" );
...
}
And the html to go with that, you'll see I tried passing in this.
<li style="cursor:pointer" ng-repeat="n in PageCount" ng-class="{active: n==1}"><a ng-click="Paginate( this, n )" href="#"><% n %> <span class="sr-only">(current)</span></a></li>
But that doesn't work as this, is not a dom element.
Directives
This could be done with Directives, these allow you to define custom markup ie. an element, an attribute name, it even can hook onto class names. Then from that you can attach all sorts of stuff.
https://egghead.io/lessons/angularjs-first-directive
So you could make a "Pagination" directive.
and that would then be used something like:
<my-pagination pages="arrayInScope">
You can then provide an external template or even a string of markup (eww) for what needs to be either IN this element or to replace this element.
Another Solution
But this is a quick way and I guess it doesn't really need any more over complicating anyway.
Example on CodePen
From the markup I am calling the paginate function parsing ng-repeat's provided $index. In the paginate function in the js i then set it.
As angular digests this: ng-class="{ active : page == current }" will then re-evaluate.
But if you need access to the element for some reason then use directives. jQuery is best avoided when using Angular, if it is just a class change or a visibility toggle etc. then its best to let Angular do it for ya'
Hope that helps.

Angular ng-class code

I am very new to Angular and I have this piece of code:
<nav ng-controller="navController">
<ul>
<li ng-class="{'active': location('/') || location('/homepage')}">
view puppies
</li>
</ul>
</nav>
With the corresponding controller:
obApp.controller('navController', function($scope, $location) {
$scope.location = function(loc){
if(loc == $location.path()){
return true;
}else{
return false;
}
}
})
Now everything works fine. When I'm on the homepage the list element gets the class active added to it, HOWEVER there is also the angular ng-class directive code shown in the source, like so (this is the HTML source output of the browser):
<li ng-class="{'active': location('/') || location('/homepage')}" class="active">
view puppies
</li>
Should the list element not look like: <li class="active">view puppies</li> ? Logic would dictate that it should look like it is now, because ng-controller="x" shows in the source as it is as well. However that's a hook if you will. The other one's an expression. It looks funky and I'm not sure if it's ok or not. Please, if you answer, detail your answer.
There is nothing wrong with your code, ng-class directive doesn't remove the attribute ng-class from the element when the expression evaluates to true, since you could have more than one expression/class in your ng-class attribute value.
Additionally if the directive did remove the attribute (ng-class) angular would have no way of knowing that you have applied that directive to the element. For example, your element had ng-if on it, it would be removed from the DOM and appended conditionally.
Example
ng-class="{'active': location('/') || location('homepage'), 'disabled': location('private'))}"
Such usage of ng-class is non-effective because $scope.location method will be called at least twice (for location('/') and location('homepage')) each digest loop. There could be a lot of digest loops due to balancing mechanism.
Better approach is to calculate if element is active on location change.
Controller:
$scope.$on('$locationChangeSuccess', function(){
$scope.isHomePage = location('/') || location('/homepage');
});
Markup:
<nav ng-controller="navController">
<ul>
<li ng-class="{'active': isHomePage}">
view puppies
</li>
</ul>
</nav>

Angular JS ng-repeat and the 'this'

i'm new in AngularJS, but did some jQuery before. i've got a problem to understand how to get the clicked element / it's parent to make some changes like change the text, an icon or a class in the item where i made the click.
the simple HTML:
<ul ng-controller="basketCtrl">
<li ng-repeat="item in item">
<button ng-click="addToBasket(Itemid,this,whatever)">
<i class="myBasketicon">
<span>Buy now</span>
</button>
</li>
</ul>
what i want to do:
$scope.addTobasket = function (id, elem, whatever){
// to some JSON-Server-stuff - that works perfect
// now my problems, :
//change this -> myBasketIcon -> myOKicon
//change this -> span text Buy now-> Thanks for buying
// give the this -> li an class => 'changed'
}
I really tried a lot, f.e with ng-model in the tags, arrays... search the web half the day... but didn't find anything that matches my problem.
Maybe it's just the way of thinking not the angular way... so please help :O)
Kind regard from Hamburg, Germany
Timo
You should be able to do this by changing a property (angular way), no need to access the element in the ng-click handler,and using ng-class and angular binding on that property.
<ul ng-controller="basketCtrl">
<li ng-repeat="item in items" ng-class="{'changed': item.added}">
<button ng-click="addToBasket(item)">
<i ng-class="{'myBasketicon':!item.added,'myOKicon':item.added }">
<span>{{item.added ? "Thanks for buying" : "Buy now"}}</span>
</button>
</li>
</ul>
and in your handler just do:
$scope.addTobasket = function (item){
item.added = true;
}
Most cases, whole purpose of using angular is to avoid DOM manipulation and let angular manage it, you just deal with the models/viewmodels and bindings.
You should add methods for the icon class and text that change their results based on the state of the object, or use custom a custom directive. You definitely don't want to be doing any DOM manipulation (changing text/classes etc) the way you would have done with jQuery.
For the method-based approach, something like this for your markup:
<li ng-repeat="item in item">
<button ng-click="addToBasket(item)">
<i ng-class="getClass(item)">
<span>{{getMessage(item)}}</span>
</button>
</li>
and on your controller:
.controller('ShoppingListCtrl', function($scope) {
$scope.getClass = function(item) {
return item.inBasket ? 'myOkIcon' : 'myBasketIcon';
};
$scope.getMessage = function(item) {
return item.inBasket ? 'Thanks for buying' : 'Buy now';
};
})
This could also be done with a custom directive which is a super powerful way to do things (and definitely worth figuring out) but may be overkill for just starting out. If you find you are adding a lot if methods for doing these sorts of things go with directives.

Getting the jQuery object that has been clicked on in angular via $event

I have a list like this. It is pre rendered and so cannot make use of anything attached to ng-repeat.
<li ng-class="{ 'active': 1 == selectedIndex }">
Item 1
</li>
I want to be able to toggle the class of the <li> when the a is clicked.
Looking at some other answers to similar questions on here, it seems as though there is a variable associated with ng-repeat which means you can use an $index variable to achieve this. As this list is pre-rendered this is not available and so I guess I have to do it the jQuery way.
I see that I have access to an $event object but event.target only gives me the DOM element, I would like to be able to convert it into a jQuery object. Is this possible?
You have to think about this in a different way than you would normally. You can't modify the DOM within angular like you do with plain old jquery. Here is what you should do:
<li ng-class="{ active: selectedItem == item }" ng-repeat="item in list">
{{ item.name }}
</li>
Then in your controller:
$scope.selectedItem = null; // if this is loaded from a service then you can set it after it loads.
$scope.itemSelected = function( item ) {
$scope.selectedItem = item;
}
No need to play around with indexes, jquery, or one off code.
Charlie

Resources