I want to add a class to an element if the title of the page includes "Home".
Currently, I use this:
<li data-bind="css: { 'active': function(){return document.title.indexOf('Home') > -1;}}">
However this does not work, this function does not get executed...
How do I make this work?
Knockout wraps the value in a computed function in a way that resembles this:
Input: "yourClassName": whateverYouPutIn
Output: "yourClassName": ko.computed(function() { return whateverYouPutIn; })
In your case, you put in a function, which will result in a "truethy" value setting the active class. You can fix this by:
Option 1 (quick): Don't wrap it in a function
<li data-bind="css: {
'active': document.title.indexOf('Home') > -1
}"></li>
Option 2 (not recommended): Fix the typo (funciton) and call the function
<li data-bind="css: {
'active': (function() {
return document.title.indexOf('Home') > -1;
}())
}"></li>
Option 3: Add these kinds of properties to your viewmodel
var vm = {
// set when VM is initialized
isActive: document.title.indexOf("Home"),
// if you want to check the title during data-binding
isActiveDuringBind: function() {
return document.title.indexOf("Home");
}
}
With either
<li data-bind="css: {'active': isActive }"></li>
or
<li data-bind="css: {'active': isActiveDuringBind() }"></li>
Note that using the DOM api outside of custom bindings is considered "bad practice"... But in this case I guess you can get away with it...
Note that because there are no observable values being used, your class will not toggle when you change document.title.
Related
I am using Vue.js to create a form. Currently my HTML looks like this:
<div class="form-group m-0" role="group">
<label class="d-block form-label" for="code_part3">Last Three</label>
<div>
<input class="form-input form-control is-invalid hasError" type="text"><!----><!----><!---->
</div>
</div>
When a user focuses on an input, or changes a select box, a class (.focused) is added to the main parent (.form-group). This animates the label to sit on top of the input/select box. And on blur a method is ran to check if the input value is to empty.
resetAnimateInputLabel() {
var target = event.currentTarget;
var inputValue = event.currentTarget.value;
var parentOfParent = target.parentElement.parentElement;
if (inputValue == "") {
//remove focused class to Parent of Parent to animate
parentOfParent.classList.remove("focused");
}
}
However I am using localStorage which will prepopulate the input fields when a user returns to the page. This causes an issue where the label is restored back to its original position (within the input itself) and both text clash.
What is the best way to solve this, so that on page load, if an input or select box is empty, for the label to remain animated?
I have tried the following:
Created a new method:
checkInputEmpty() {
var inputs = document.getElementsByClassName("form-input");
for (var i = 0; i < inputs.length; i++) {
if (inputs[i].value == "") {
inputs[i].parentElement.parentElement.classList.remove("focused");
} else {
inputs[i].parentElement.parentElement.classList.add("focused");
}
}
}
and called it here:
mounted() {
this.checkInputEmpty();
if (localStorage.email !== "undefined") {
this.form.email = localStorage.email;
}
}
You could do two things differently to handle this in a simple and elegant way:
First - Checking input values:
Step 1: Having data property for storing the input value
data () {
return {
inputValue: '',
}
}
Step 2: Binding your input to that property
<input v-model="inputValue" />
Step 3: Investigating the data property and handling both cases as needed (wherever needed)
if (this.inputValue) {
// here, the input value is non-empty ("", null, undefined would evaluate to false)
} else {
// here, the input value is empty
}
Second - Class toggling
Step 1: You don't have to set and remove classes in this complicated way:
inputs[i].parentElement.parentElement.classList.add("focused");
Instead you can, again, create a data property for storing toggled state of the focused class:
data () {
return {
inputValue: '',
formFocused: false
}
}
Step 2: Dynamically assign the class to your element like this:
<div
class="form-group m-0" role="group"
:class="{ 'focused' : formFocused }"
>
...
</div>
Step 3: And handle the property as needed, in your case probably like this:
if (this.inputValue) {
this.formFocused = false;
} else {
this.formFocused = true;
}
Which is the same as:
this.formFocused = !this.inputValue;
For more info:
https://v2.vuejs.org/v2/guide/forms.html#v-model-with-Components
https://v2.vuejs.org/v2/guide/class-and-style.html
I am trying to use both regular class attribute and class conditional attribute in an html element below:
<ul *ngIf="showDropdown">
<li class="active" [class.active]="sIndex === x" (click)="dropdownSelect($event.target.value, x)" value="18203">All Groceries</li>
<li [class.active]="sIndex === x" (click)="dropdownSelect($event.target.value, x)" *ngFor='let i of lists; let x = index' value="{{i.id}}">{{i.name}}</li>
</ul>
I have the first li I want to be active but have click attribute which toggles click. Any ideas?
You can use
[ngClass]={"active": sIndex === x}
But in your code you already have the class "active" by default, so nothing will change, always going to have the active class.
explain more, what you want to do.
You're example is looking weird ,
the first li is always active no matter what will happen , and both of li have the same condition that makes them active .
Try this
<ul *ngIf="showDropdown">
<li [class.active]="sIndex === x" (click)="dropdownSelect($event.target.value, x)" value="18203">All Groceries</li>
<li [class.active]="sIndex !== x" (click)="dropdownSelect($event.target.value, x)" *ngFor='let i of lists; let x = index' value="{{i.id}}">{{i.name}}</li>
</ul>
This is the right way to add a class in case its true
[class.active]="sIndex === x"
I figured the answer out. So if you want to have a conditional where active class changes when you click, but want an active class on any element first you do the following:
[ngClass]="{'active': sIndex === x || first}" (click)="dropdownSelect($event.target.value, x)"
add a conditional parameter with your toggle active class on click.
constructor(private api: API, private http: Http) {
this.first = true;
}
you set first to true in in your constructor. then when someone clicks, you put it into function and set it to false;
dropdownSelect(v, x: number) {
this.filter = v;
this.sIndex = x;
this.showDropdown = !this.showDropdown;
this.first = false;
}
I am new to AngularJS and have some trouble understanding the concept of scope in Angular. I have read some posts on stackoverflow as well as online articles, which advise me to create a custom directive to create an isolate scope, but I am getting nowhere...
As for the project I'm working on, I am trying to make a button that when clicked, will trigger a textarea. However, because of ng-repeat, the textarea is triggered for all buttons while I click only one.
My .js file:
angular.module('myApp')
.controller('myCtrl', function ($scope, Question) {
scope.visible = false;
scope.toggle = function() {
scope.visible = !scope.visible;
};
.directive("myDirective", function () {
return {
scope: {
ngClick: '&',
ngShow: '&'
}
}
});
Here is my HTML file:
<ul>
<li ng-repeat="object in objectList">
<button type="text" myDirective ng-click="toggle()">Click</button>
<textarea myDirective ng-show="visible"></textarea>
</li>
</ul>
Angular is creating child (NOT isolated) scope when ng-repeating, try this out, when you ng-init a variable, it is only visible within that repeat div.
<div ng-repeat="i in [0,1,2,3,4,5,6,7,8,9]" ng-init="visible=false">
<button ng-click="visible=!visible">Toggle</button>
<h1 ng-show="visible">look at me!</h1>
</div>
Plunker
There is no need to use a directive. You need to use object in the foreach to refer each item in the loop.
Add visible to each object in objectList:
$scope.objectList = [
{ visible: false },
{ visible: false },
{ visible: false }
];
Then the toggle button will need to pass the object to toggle:
$scope.toggle = function (object) {
object.visible = !object.visible;
};
The ng-show will need to check object.visible and ng-click will need to pass the object:
<button type="text" ng-click="toggle(object)">Click</button>
<textarea ng-show="object.visible"></textarea>
Plunkr
I am trying to add the class by ng-class in my app.
I have something like
html
<li ng-repeat="item in items"
ng-click="getItem(item)"
ng-class="{shine : item.isPick}" // shine class will make the font color change to red
>{{item.name}}
</li>
In my controller
$scope.getItem = function(item) {
$scope.items.forEach(function(item){
item.isPick = false; // remove all the items that have shine class
})
item.isPick = true; // adding the shine class back
}
Basically I want to remove every other 'shine' class except the selected item when user clicks it. My codes above work but I am thinking there is a better way to do it instead of looping all the items. Can someone help me about it? Thanks a lot!
Use the $index in the repeater, and add the class when that index is selected:
<li ng-repeat="item in items"
ng-click="getItem($index)"
ng-class="{shine : activeIndex == $index}"
>{{item.name}}
</li>
$scope.getItem = function(index) {
$scope.activeIndex = index;
}
Check this PLNKR, i have one list with id myMenuList, this id am accessing in script.js to display Numer of li and UL width by $scope.mml = angular.element(document.getElementById('myMenuList'));.
But as per requirement I should not be accessing it in controller like this. Is there any alternative we can do by keeping the same behaviour?
HTML CODE
<div class="menucontainer left">
<ul id="myMenuList" ng-style="myStyle">
<li ng-repeat="item in items"> {{item.name}} </li>
</ul>
</div>
JavaScript
$scope.mml = angular.element(document.getElementById('myMenuList'));
$timeout(function() {
$scope.numerofli = $scope.mml.find('li').length;
$scope.ulwidth = $scope.mml[0].clientWidth;
}, 1000);
Use only
var ele = angular.element('#id');
var ulwidth = ele[0].clientWidth;
Demo Plunker
Implement a directive with isolated scope to encourage modularity and re-use:
app.directive('myMenuList', function($timeout) {
return {
restrict: 'A',
scope: {
myMenuList: '='
},
link:function($scope, $element, $attr) {
$timeout(function(){
$scope.myMenuList.numerofli= $element.find('li').length ;
$scope.myMenuList.ulwidth= $element[0].clientWidth;
}, 1000);
}
}
});
To use it, initialize an output model from inside your parent controller:
app.controller('scrollController', function($scope, $timeout) {
$scope.output = {};
...
});
Place the my-menu-list attribute on the ul element, and pass it the model defined earlier:
<ul my-menu-list="output" ng-style="myStyle">
<li ng-repeat="item in items"> {{item.name}}
</li>
</ul>
When the directive executes it will populate the model with two properties, which you can then reference in your HTML:
<p><b>Numer of 'li': {{output.numerofli}}</b></p>
<p><b>Width: {{output.ulwidth}}</b></p>
Use query selector
angular.element( document.querySelector( '#id' ) );
While the other answers might be correct on the alternative of getElementById, your code should be rewritten in the Angular-way. See this plunker.
It should reflect your requirements anyway.