How would I create this toggleClass function as an AngularJS directive? - javascript

Before I knew about things like Angular and jQuery, there was plain old Javascript like this:
function toggleClass(e, c) {
var classes = e.className.split(' ');
var match = false;
for(var i=0; i<classes.length; i++) {
if(classes[i] === c) {
match = true;
classes.splice(i,1);
break;
}
}
if(!match) classes.push(c);
e.className = classes.join(' ');
}
I've used this in the past to toggleClass name in an onclick event like so:
<div onclick="toggleClass(this,'foo')"></div>
Here is a working JSFiddle.
How would I implement this as a directive in Angular?

You can use AngularJs' ng-class directive instead of creating another directive.
angular.module('demo', [])
.controller('Ctrl', function($scope) {
$scope.toggleRed = true;
});
.box {
padding: 50px;
display: inline-block;
background-color: #efefef;
}
.box.red-box {
background-color: red;
}
<div ng-app="demo" ng-controller="Ctrl">
<div class="box"
ng-class="{'red-box': toggleRed}"
ng-click="toggleRed = !toggleRed">Click Me</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>

Angular is not jQuery, so your thought process should not be about adding removing classes or showing hiding elements or anything to do on these lines.
Please refer to this SO post for some good pointers "Thinking in AngularJS" if I have a jQuery background?
In Angular model drives the view.
What you are doing should be done using the standard ng-class directive.
Let's say you have a grid of users and you want highlight rows when the user click on the row signifying the user has been selected. The way you would go about it would be to define the row html as
<tr ng-repeat='user in users' ng-click='user.selected=!user.selected' ng-class={'active': user.selected}>
</tr>
Now the state of user.selected drives the view and toggles the class on every click.

Related

Iteration on CSS class array and Toggle class on click

I have this code
The problem is I need to toggle class on click. I have classes
.col
.col-1 / .col-2 / col-3 etc.
and I need to apply on click to the right .col-1 / col-2 an expand class, so it would like
.col .col-1 .col-1--expand
.col .col-2 .col-2--expand
Before I had this on hover in CSS and it works, but make it on click is little problematic. I searched whole day Javascript and jQuery cases, but I haven't found any right solution to it.
What I learned, I must use forEach function in Javascript, but a solution in jQuery is also what I want.
I tried something like this on my project, but I'm still bad at Javascript :(
if (document.querySelector('.slider__col')) {
const expandElement = element.querySelectorAll('.slider__col')
expandElement.forEach(element => {
element.addEventListener('click', function() {
element.classList.toggle("");
})
})
You can use a regular expression to match element classes and toggle the expand class on that element when clicked.
var matched = this.className.match(/col-\d{1,2}/);
This will find any classes in your element's class attribute that contains col- followed by any numbers up to two digits so you can cater for 1-99.
matched.length && (matched = matched.pop())
.match() returns an array of matches so you can determine if any matches were found and pop the first match off of the array.
var expandClass = matched + '--expand';
Because you're matching, for example, col-1 you can use this string and append --expand to make col-1--expand.
$(this).toggleClass(expandClass);
You can use jQuery's toggleClass to add/remove the expandClass depending on the class's presence. See col-3 for demonstration.
$(document).on('click', '.col', function () {
var matched = this.className.match(/col-\d{1,2}/);
if (matched.length && (matched = matched.pop())) {
var expandClass = matched + '--expand';
$(this).toggleClass(expandClass);
}
});
.col {
background-color: blue;
color: white;
padding: .25rem 1rem;
margin: .25rem 0;
}
.col-1--expand,
.col-2--expand,
.col-3--expand,
.col-4--expand,
.col-5--expand,
.col-6--expand {
background-color: green;
padding-top: 1rem;
padding-bottom: 1rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="grid">
<div class="col col-1">1</div>
<div class="col col-2">2</div>
<div class="col col-3 col-3--expand">3</div>
<div class="col col-4">4</div>
<div class="col col-5">5</div>
<div class="col col-6">6</div>
</div>
Welcome to StackOverflow! Since you want a solution in jQuery, let me give you some guidance.
1. The forEach function is in jQuery available as the
.each() method. Making your code:
// Select your classes, for each of them run a function
$('.col-1').each(function() { });
2. To make something happen on a click, jQuery has a .click() method available, it will call a function on click. Making your code:
$('.col-1').each(function() {
// Click method:
$(this).click(function() { } );
});
// Or just do:
$('.col-1').click(function() { });
jQuery does not need a loop and can bind the click method to all classes by itself. There are cases where it still might be useful, but let's keep it basic for now and get your code working!
3. Adding the new CSS class. In jQuery there are many methods you can use for it. In this case you are probably looking for the .toggleClass() method. Other useful ones might me .addClass() and .removeClass(). Making your code:
$('.col-1').click(function() {
$(this).toggleClass("col-1--expand");
});
// Or just add it:
$('.col-1').click(function() {
$(this).addClass("col-1--expand");
});
Done, repeat for other classes where you want to do some magic. Next time try spending a day on the jQuery API Documentation. All the methods are there, with great examples included! Best of luck with it.

Implementing a "Load More" button to load more elements when its clicked

I want my li elements to be hidden and when my state loads I want to display only the first two elements and when I click on a button o show the next two elements and so on.
This is how it should work :
http://jsfiddle.net/zuyvgwx3/2/
In my angular application I display my li elements as following :
<ul id="myList">
<li ng-repeat="offre in offres |orderBy:'-dateCreation'|filter:{etat:'true'}">
<div class="box">
<!-- HTML Code -->
</div>
</li>
</ul>
I have li elements set to display:none; and a javascript code that shows the first two elements as following :
<script>
size_li = $("#myList li").size();
x=2;
$('#myList li:lt('+x+')').show();
$('#loadMore').click(function () {
x= x+2;
$('#myList li:lt('+x+')').show();
});
</script>
The problem is when my state loads all the li elements are shown and not only the first two elements, as you can see in the fiddle I attached it works but it doesn't work when I use it with ng-repeat.
How can I solve this?
As you are using Angular, you should try to avoid directly manipulating the DOM in this way where possible. Instead you should take advantage of Angular directives to control how elements are displayed, in this case ng-show.
You are already storing the list in the $scope.offres. You just need to keep track of how many items to show and only show it if the $index available in ng-repeat is lower than that value:
angular.module('myapp', []).controller('ctrl1', function($scope) {
// set up the list with dummy data
$scope.offres = [];
for (var i = 0; i < 11; i++) {
$scope.offres.push(i + 1);
}
// set initial index
// we will use this variable to control the display - see ng-show="" in HTML
$scope.loadIndex = 2;
// function to increase visible items
$scope.showMore = function() {
// don't increment if at the end of the list
if ($scope.loadIndex < $scope.offres.length) {
$scope.loadIndex += 2;
}
};
// function to decrease visible items
$scope.showLess = function() {
// don't decrement if at the start of the list
if ($scope.loadIndex > 2) {
$scope.loadIndex -= 2;
}
};
});
#loadMore {
color: green;
cursor: pointer;
}
#loadMore:hover {
color: black;
}
#showLess {
color: red;
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myapp" ng-controller="ctrl1">
<ul id="myList">
<li ng-repeat="offre in offres" ng-show="$index < loadIndex">{{offre}}</li>
</ul>
<div id="loadMore" ng-click="showMore()">Load more</div>
<div id="showLess" ng-click="showLess()">Show less</div>
</div>
As you can see, it is then trivial to implement showing less items, as you are just changing a number in your controller.
You can directly use Angular's ng-show/ng-hide and view binding to achieve this. You don't need to use jQuery for this. Here is a sample example:
var app = angular.module("sa", []);
app.controller("FooController", function($scope) {
$scope.offers = [];
// Generating dummy data
for (var i = 0; i < 100; i++) {
var number = i + 1;
$scope.offers.push({
number: number,
name: 'Offer ' + number
});
}
$scope.showFirstTwo = function() {
var shown = 0;
angular.forEach($scope.offers, function(offer) {
if (!offer.isVisible && shown < 2) {
offer.isVisible = true;
shown++;
}
});
};
// At load, display first two by default
$scope.showFirstTwo();
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="sa" ng-controller="FooController">
<ul>
<li ng-repeat="offer in offers" ng-show="offer.isVisible">
Offer ID: <strong>{{offer.number}}</strong>
<br>Offer name: <strong>{{offer.name}}</strong>
</li>
</ul>
Load more
</div>
When using AngularJS I personally try to avoid writing custom JQuery code for DOM manipulation.
To implement the functionality that you have described, I would have taken following approach.
Load the items in a temporary array, say offersHidden.
When the items are loaded, shift() the 2 items from offersHidden and push() to offres
When the user clicks on Load More, do the same thing: offres.push(offersHidden.shift())
That way you let AngularJS take care of UI manipulation and avoid any conflicts.
As said by Rhumborl, you should use ng-show to handle this.
When you click on the button, just increment a variable and show items only if variable > index.
<li ng-show="variable > index"></li>
$('#loadMore').click(function () {
$scope.variable += 2;
});

How to get an element within an ng-repeat by its $index value

I'm using ng-repeat, but I'd like to modify iterations if they satisfy some condition. My question is, what is the best way to get those specific iterations that should be modified?
As it stands right now, I've got it working using the :nth-child selector, but I'm wondering if there is a more angular way to do this using $index, or any other more angular way?
HTML Snippet:
<div class="line_row" ng-repeat="x in program">
{{x.current_state}
</div>
JS Snippet:
for (j = 0; j < $scope.program.length; j++) {
if ($scope.reader_location[j] == someCondition) {
// this is the line that I'm interested in replacing.
$(".line_row:nth-child("+(j+1)+")").css("background-color","red")
}
}
I think you could ng-class here based on expression evaluation it will place a class on that element.
HTML
<div class="line_row" ng-repeat="x in program" ng-class="{red: methodCall(x)}">
{{x.current_state}}
</div>
Code
$scope.methodCall = function(x){
return x.current_state == 'something'
}
CSS
.red {
background-color: "red"
}
You can use ng-class with the condition. If the expression of ng-class is true, it will add a CSS class, in this case, I named it redbackground.
HTML
<div class="line_row" ng-repeat="x in program track by $index"
ng-class="{redbackground: $index === someCondition}">
{{x.current_state}
</div>
CSS
.redbackground { background: red; }
Here is a tutorial of how to use ng-class in many different ways.

AngularJS changing class without ng-class

I'm looking to enable users to change the class from incomplete to complete on button click when function(response) returns 1. I have tried using issues, however; It doesn't work well as the HTML elements are loaded with a PHP loop so an ng-class expression doesn't work. This is because an if statement is run checking if it is incomplete or complete while the AngularJS expression wouldn't be able to check the database in this sense.
I added the 'active' variable, but I cant seem to put this into play without ng-class. Is there an alternative to jQuery's class add/remove? Or can someone think of another solution.
HTML:
<div class='task col-md-4 incomplete'>
<button data-ng-click='toggleTask(".$lesson_id.", 0)' class='btn btn-default'>Mark as complete</b></button>
</div>
AngularJS:
var app = angular.module('training-center', []);
app.controller('task-manager', function(\$scope, \$http) {\
$scope.toggleTask = function(id, active) {\
$scope.id = id;\
$http.post('app/modules/Controller.php?action=toggleTask', {
task_id: id,
active: active
}).
then(function(response) {
if (response.data == '1') {
//What do I place here?
}
},
function(response) {
});
};
});
You do not have to use ng-class - you can use regular class and put a parameter in it with {{}}. Then just change the variable in the $scope, and it will automatically adapt its behaviour.
Here is an example:
<div ng-app>
<div ng-controller="testCtrl">
<div id="test" class="{{className}}">
{{className}}
</div>
Click
</div>
</div>
And the controller code (it's ugly, but you'll get the point):
function testCtrl($scope) {
$scope.className = "red";
$scope.increase = function() {
if($scope.className == "red")
$scope.className = "blue";
else if($scope.className == "blue")
$scope.className = "green";
else if($scope.className == "green")
$scope.className = "red";
}
}
(Of course, classes are trivial):
.red { color: #FF0000; }
.blue { color: #0000FF;}
.green { color: #00FF00;}
This works just fine for me. I have not tested it out in an environment where the body of the page is generated by PHP, but should not make a difference.

knockout js not updating from view model

So I have a the following textarea in my view that I am attempting to update via a knockout binding.
Here is the code from the View:
<textarea disabled id="CCHPISentence" style="width:99.7%; height:75px; resize: none; font-family:Verdana; overflow: auto;" data-bind="text: fullSymptomTextObservable"> </textarea>
Here is the Jquery function that applies the bindings, I am wondering if my issue is here:
$(function () {
ko.applyBindings(symptomTextViewModel, document.getElementById("CCHPISentence"))
})
Here is my ViewModel:
function symptomTextViewModel(fullText) {
if (fullText === undefined)
{ fullText = ""}
this.fullSymptomTextObservable = ko.observable(fullText.toString())
}
Here is a snip from the js function that calls my ViewModel. I am building the fullText variable in this 2nd js function:
//FINAL PARAGRAPH KNOCKOUT VM MAPPING
fullText = sentence1 + sentence2 + sentence3 + sentence4 + sentence5
var symptSentViewModel = new symptomTextViewModel(fullText)
symptomTextViewModel(fullText);
Thanks so much to anybody in advance who can assist me here. I feel like I am missing something stupid, I have tried this every different way that I can think of with no luck.
it would be easier to make fullSymptomTextObservable a pureComputed observable. That way as the various sentences change so will the full sentence. This way you are taking advantage of knockoutjs.
function SymptomTextViewModel(fullText) {
var self = this;
if (fullText === undefined) {
fullText = ""
}
self.fullSymptomTextObservable = ko.observable(fullText.toString())
}
var vm = new SymptomTextViewModel('This is a test paragraph. Watch for the alert();');
ko.applyBindings(vm,document.getElementById("CCHPISentence"));
alert("about to use the vm variable to access the text.");
vm.fullSymptomTextObservable('Changing it to something else');
alert("about to use the ko.dataFor function");
var testVm = ko.dataFor(document.getElementById("CCHPISentence"));
testVm.fullSymptomTextObservable("Maybe this would be better suited to what you need.");
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<textarea disabled id="CCHPISentence" style="width:99.7%; height:75px; resize: none; font-family:Verdana; overflow: auto;" data-bind="text: fullSymptomTextObservable"></textarea>
First of all I would fix all missing semicolons in your code. Than check if scope of 'this' in your view models function is correct. Some info from browser console would be also helpful. Check if there isn't any errors which knockout would throw.

Categories