*ngIf does not toggle back to false in else block - javascript

I am using Angular7, npm 6.4.1, node 10.15.3.
I am just playing around with ngIf, this does nothing useful. I am using two buttons to toggle back and forth between displaying and hiding a header. While it was just an ngIf statement, it was working fine with the following code:
<p>Do you want to see the last header?</p>
<button (click)="yesHeader()">Yes please!</button>
<button (click)="noHeader()">PLEASE G-D NO!</button>
<h2 *ngIf="displayHeader" style="color:blue">Here it is in all its glory!</h2>
and
public displayHeader=false;
yesHeader(){
this.displayHeader=true
}
noHeader(){
this.displayHeader=false
}
And that worked fine, showing the header when clicking the yesHeader button and hiding it again when clicking the noHeader button.
However, when I added an ngIf Else block, it stopped toggling back and forth. The change was to the following:
<p>Do you want to see the last header?</p>
<button (click)="yesHeader()">Yes please!</button><button (click)="noHeader()">NO! NO!</button>
<h2 *ngIf="displayHeader; else noHeader" style="color:blue">Here it is in all its glory!</h2>
<ng-template #noHeader>
<h5>You don't know what you're missing!</h5>
</ng-template>
Now, it displays You don't know what you're missing! at start, and switches to the header if I click yes. But it doesn't toggle back to hiding the header if I click the no button after that.
What am I doing wrong and how can I get it to toggle back?
As an added datapoint, if I leave the else block, but take else noHeader out of the ngIf statement, it still doesn't toggle back. So seemingly the existence of the ng-template is what is stopping things up.

The issue is that there is a name conflict of noHeader. It being used in your example as both a method on the component class as well as the name of the ng-template. Try changing either the name of the method or name of the template:
<p>Do you want to see the last header?</p>
<button (click)="yesHeader()">Yes please!</button><button (click)="noHeader()">NO! NO!</button>
<h2 *ngIf="displayHeader; else noHeaderTemplate" style="color:blue">Here it is in all its glory!</h2>
<ng-template #noHeaderTemplate>
<h5>You don't know what you're missing!</h5>
</ng-template>
Here is an example in action.

Your template is called the same as function: noHeader.
You have name collision problem, you can't call noHeader function, Angular thinks you're referencing noHeader template reference.
<ng-template #noHeaderTemplate>
<h5>You don't know what you're missing!</h5>
</ng-template>
Or
hideHeader() {
this.displayHeader = false;
}
A proper IDE should tell you "Member noHeader is not callable".

Related

How to send data from v-for to event handler in Vue?

I am having problems with sending the right data to the event handler of a subcomponent in my Vue component.
Here is my code:
<template>
<div>
<div v-for="item in [1, 2, 3]">
<div #click=test(item)>test</div>
<ConfirmModal v-if="showModal" #confirmed=test(item) />
<button v-on:click="showModal = true" ></button>
</div>
</div>
</template>
<script>
import ConfirmModal from 'ConfirmModal'
export default
{
components: {ConfirmModal},
data()
{
return {showModal: false}
},
methods:
{
test(item)
{
console.log(item);
this.showModal = false;
},
},
}
</script>
And here is the code for a subcomponent:
<template><div class="modal" #click.self="$emit('confirmed')">subtest</div></template>
So, basically, I here have an array of numbers and am using the v-for to iterate over an array.
For each item one test label, one subcomponent (containing a subtest label) and one button is created. When button is clicked, subcomponent is shown. When I click on the subcomponent (subtest label) it disappears.
div is a standard component which emmits #click events when it is clicked upon.
confirmModal is a very simple component which listens for #click events and redirects them into #confirmed events.
So, this code generates 3 times 3 items. When I click on any of the labels, test method is called which logs the value of the item.
When I click on any of the test labels, I get the correct value in the console, that is, 1 for the first item, 2 for the second item and so on.
But, when I click on any of the subtest labels, I allways get the 3, whichever link I clicked. (If I change the order of items in the array, then I allways get whichever item I put on the last place in the array).
I do not understand what is going on here, it is the same method called on the same way. Why is 3 allways logged in console when I click on the subtest label?
If you wonder what is class=modal in my subcomponent used for, I have no idea. This is a skeleton of two components in a very complex project consisting of thousands of files built on top of the Liferay, and there are more then ten files where is .modal defined in many directories. So I am not sure from which file does this particular component load this class. I did not throw it out of this skeleton code just because when I remove this class from the div in the subcomponent, everything works fine. I have no idea why. How can a class stop the correct data from being received? I do not get this.
I know that this bug may not be reproducable, because I did not provide the definition of the .modal style, but I posted it anyway, because I hope that you can see something else in the code what is wrong or propose another way how to execute the test method on #confirmed event.
I am using Vue 2.

Retain state of multi-level navbar/sidebar on new page

I'm very new to programming & working on creating a website for a work project.
In it, there will be a multi-level (w/sub-menus) vertical sidebar on each page.
The problem I'm facing is that every time a user clicks on one link, the sidebar resets to its original state & will have to redo the same thing & not very UX friendly.
I took the template of the accordian sidebar from here.
I've looked at various search results on both stack overflow & google, but can't seem to understand how to get it working to retain the state of the sidebar, regardless of how many levels are opened.
Can someone please help me with the JS code to get it working?
UPDATE:
Nathan, thanks for writing mate! I really appreciate the help.
So based on your suggestion, I've written the following (shoddy) code that injects the 'checked' attribute to the input element.
But it isn't transferring over to the new/redirected html page when a user clicks on one of the sub-menus. What am I missing here?
var menuIndex = -1;
//extract all the input elements
var inputs = document.querySelectorAll('.parent-menu');
//Find index of the element from the array that has "checked == true"
function indexFinder() {
for (var i = 0; i < inputs.length; i++) {
if (inputs[i].checked == true) {
menuIndex = i;
console.log(menuIndex);
}
};
}
//Function to set/inject the attribute
function attributeSetter() {
inputs[menuIndex].setAttribute('checked', 'checked')
}
//When a user clicks literally anywhere, it'll run the indexFinder function
//to check if any of the input elements were expanded (i.e. checked == true)
window.addEventListener('click', () => {
indexFinder();
});
//Run the attributeSetter function when a page loads
window.addEventListener('DOMContentLoaded', () => {
attributeSetter();
});
Welcome to the world of programming! Hopefully I can help you out a little!
So what you're asking is something that can easily get a little complicated.
In order to achieve what you're trying to do you need to specify how you want your menu to look on each individual page!
Allow me to present a few menu options for an imaginary site:
Home
Contact
Email
Mail
About
The Company
Our Owner
I've indented the page names based on how we want them to show up in our menu.
So for example you may click on "Contact" and it drops down with the Email and Mail options.
Well, if you take your regular code from that webpage and copy and paste it everywhere. Any time you reload a page (or travel to another page with the same code) it's gonna reset the code! Thus "closing" the menu. Think of it as some sort of multi-dimentional sci-fi. When you load a webpage, you are accessing the main flow of time, any time you make an update to that page it takes you to an alternate reality with that change! but once you reload the webpage you jump back to the main timeline as if you never made that change (when you get into more advanced web dev, this analogy will break down but it should work to help your understanding for now.)
So let's say I click on the Contact > Email option and it takes me to the Email page. Well, in order to make it seem like my changes to the menu bar (clicking "Contact" to expand the dropdown) are still active. I need to hardcode the change into the Email page!
Here's some sample code:
<nav class="nav">
<a class="navOption">Home<a>
<a class="navOption">Contact<a>
<div class="navDropdown">
<a class="navOption">Email<a>
<a class="navOption">Mail<a>
</div>
<a class="navOption">About<a>
<div class="navDropdown">
<a class="navOption">The Company<a>
<a class="navOption">Our Owner<a>
</div>
<nav>
By default the .navDropdown will be closed. However when we add a class to them .active they will expand! If this is my base menu, then how should I make it so that the "About" dropdown is expanded when you are on one of the About pages?
Simply by adding .active to that dropdown!
<nav class="nav">
<a class="navOption">Home<a>
<a class="navOption">Contact<a>
<div class="navDropdown">
<a class="navOption">Email<a>
<a class="navOption">Mail<a>
</div>
<a class="navOption active">About<a>
<div class="navDropdown">
<a class="navOption">The Company<a>
<a class="navOption">Our Owner<a>
</div>
<nav>
Now, my example is different from yours because it's meant more for JavaScript. However, you can use the same concept in your code too.
For you, instead of having a .active class to expand a dropdown menu. You are using a checkbox element! In your codem you have CSS which is checking to see if the checkbox is checked and then it is opening the dropdown menu if it is:
<input class="cd-accordion__input" type="checkbox" name ="group-1" id="group-1">
So, if we use this method on our example webpage. We could set it to be open by setting the checkbox to start out being checked. Like so:
<input class="cd-accordion__input" type="checkbox" name ="group-1" id="group-1" checked>
It's important to note that as you get better and better at web development (eventually learning JavaScript and a server side language such as PHP). You will be able to piece together more advanced methods to doing what we're trying to accomplish! But for now, I hope I was able to break this down for you!

CasperJS : Selecting an option in dropdown menu made of <div> and dynamic classes

After many attempts and searches here, I can't help myself fixing my CasperJS script.
Basically, I'm crawling pages of a website that lists tons of pages. The main page can be filtered after I successfully logged in (this part is done) and then, I'd like to select in dropdown menu an option to filter them out. Here's the HTML code of the drop-down :
<div class="sg-dropdown brn-filters__status js-filter-status js-status-toggle">
<div class="sg-dropdown__icon"></div>
<div class="sg-dropdown__hole sg-dropdown__hole--inactive">
<div class="sg-dropdown__item-text sg-dropdown__item-text--active js-main-status"> Unanswered </div>
</div>
<div class="sg-dropdown__hole sg-dropdown__hole--active">
<div class="sg-dropdown__item-text sg-dropdown__item-text--active"> Select </div>
</div>
<div class="sg-dropdown__items js-status-list">
<div class="sg-dropdown__item-hole ">
<div class="sg-dropdown__item-text"> All </div>
</div>
<div class="sg-dropdown__item-hole chosen-status">
<div class="sg-dropdown__item-text"> Unanswered </div>
</div>
<div class="sg-dropdown__item-hole ">
<div class="sg-dropdown__item-text"> Unresolved </div>
</div>
<div class="sg-dropdown__item-hole ">
<div class="sg-dropdown__item-text"> Resolved </div>
</div>
</div>
</div>
How can I select "Resolved" in this drop-down menu using CasperJS?
What I've noticed as potential clues:
By default, the filter "Unanswered" is applied (.js-main-status
defines this I guess).
Might be nothing relevant but as soon as I
open the drop-down menu, the first <div> changes its class to
sg-dropdown brn-filters__status js-filter-status
js-status-toggle--opened
When I select a filter (let's say "Resolved"), the chosen-status is added to its sg-dropdown__item-hole parent class. About this, sg-dropdown__item-hole finished with a double space, the last one being replaced by chosen-status when the option is clicked.
I've tried so many lines of code, from a simple .click() method to chunks of code more complex involving evaluate(), sendKeys (which I used right before to perform the login), querySelector().selectedIndex, fetchText() using XPath, and so on...
My problem, and it's on repeat with others buttons on this page, is that I have to deal with only <div> elements that have near-only classes that change as soon as an event is performed (click, scroll, etc).
Thanks for your help!
UPDATE #1
I've tried these LOC (based on this thread):
casper.then(function () {
if (this.exists('div.sg-dropdown.brn-filters__status.js-filter-status.js-status-toggle')) {
this.click('div.sg-dropdown.brn-filters__status.js-filter-status.js-status-toggle');
console.log('click dropdown');
}
else {
console.log('no click dropdown');
}
});
casper.wait(9000, function() {
console.log('wait 9 seconds 1/2');
});
casper.then(function() {
if (this.exist('div.sg-dropdown.brn-filters__status.js-filter-status.js-status-toggle.sg-dropdown--opened > div.sg-dropdown__items.js-status-list > div:nth-child(4)')) {
this.click('div.sg-dropdown.brn-filters__status.js-filter-status.js-status-toggle.sg-dropdown--opened > div.sg-dropdown__items.js-status-list > div:nth-child(4)');
console.log('click Resolved');
}
else {
console.log('no click Resolved');
}
})
casper.wait(9000, function() {
this.capture('test.png');
console.log('wait 9 seconds 2/2');
})
The first step seems to work fine as I can see wait 9 seconds 1/2 message prompted in the console. However, if I capture this step, I won't see the drop-down opened.
The second step is a complete failure. I first thought it was due to the absence of the --opened at the end of sg-dropdown brn-filters__status js-filter-status js-status-toggle class but nothing works as I got always this same error:
TypeError: undefined is not a constructor (evaluating 'this.exist')
In some previous tests, I've returned the value undefined when I was trying to read ' Resolved' into the <div>. I even returned literally nothing in the console, like blank spaces and no error and the end of the run.
Any idea anyone? 🤔
UPDATE #2
I've just added an if condition to test sg-dropdown brn-filters__status js-filter-status js-status-toggle--opened class and it seems to not exist after clicking on the drop-down.
So step 1 is not even working and now, I'm kinda lost 😕
I eventually found this : https://github.com/casperjs/casperjs/issues/1390 with a couple of links to Stack Overflow's threads often answered by #Artjom-b.
It seems that I'm in a dead end as CasperJS doesn't offer native support for selecting an option from a drop-down but in my case it's even worse as I have no <select> nor <option>. I'm sure there's a way to update the classes and reload the page consequently but it's way out of my league right now.
Anyway, I've found out another way to access the data I was looking for, I'm just digging now how to perform the scrapping faster!

Show div using Angular ng-show

I'm havings some problems with ng-show and $pristine.
Here is the code (also on CodePen):
<blockquote ng-show="!comment.author.$pristine && !comment.rating.$pristine && !comment.comment.$pristine">
<p>{{comment.rating}} Stars</p>
<p>{{comment.comment}}</p>
<footer>{{comment.author}}
</blockqoute>
When every field on the form has been filled, I want to show the div that contains my duplicate, but I want it to be hidden while some are still empty.
I tried to to use
!comment.[index].$pristine && ....
so when every field has been filled, the blockquote would become visible, but it didn't work.
Hey the way you are going the main problem will be that when the user will be filling any random data in the last text box, the moment he fills a letter the div will be visible to him - despite whatever improvements you make to the code.
What I'll suggest is - make use of ng-show="whatever" for that section that you want to show after the data has been filled.
In the beginning where your controller starts make it false $scope.whatever = false; and now it wont be visible to the user; when the user has filled all the text boxes call a trigger and check if the data is valid or not and then $scope.whatever=true; - Now your section will be visible.
To call the trigger you can do various things
- you can make use of ng-change on the last textbox and there check values of all textboxes using their specific model name, I hope you know that.
Let me know if you want further clarification on this.
I believe you can specify ng-hide as a className to hide it by default.
<blockquote ng-show="whatever" class="ng-hide"
Change this
<blockquote ng-show="!comment.author.$pristine && !comment.rating.$pristine && !comment.comment.$pristine">
<p>{{comment.rating}} Stars</p>
<p>{{comment.comment}}</p>
<footer>{{comment.author}}
</blockqoute>
To this
<blockquote ng-show="!commentForm.author.$pristine && !commentForm.comment.$pristine">
<p>{{comment.rating}} Stars</p>
<p>{{comment.comment}}</p>
<footer>{{comment.author}}, {{comment.date | date}}</footer>
</blockqoute>
Notice I'm using the form to check for the form properties, not the scope properties. Just change comment to commentForm (which is your form's name).
More info about form controllers
Working codepen
I would bind values of the form input to some variables in the controller, and on their ng-change="controller.validate()" run a validate code, so you can check if fields are not empty and input is correct and after that set isBlockquoteVisible variable, that would be in <blockquote ng-show="{{controller.isBlockquoteVisible}}" ...
<blockquote ng-hide="comment.author.$pristine && comment.rating.$pristine && comment.comment.$pristine">
<p ng-show="!comment.rating.$pristine">{{comment.rating}} Stars</p>
<p ng-show="!comment.comment.$pristine">{{comment.comment}}</p>
<footer ng-show="!comment.author.$pristine">{{comment.author}}</footer>
</blockquote>

Toggle with ! operator not working on page load JavaScript

I have an onClick (using angular.js ng-click) where i have to toggle color of a table row with on click.
This is the initial implementation.
<tr>
<td ng-class="{'setType': types.isTypeSet('TYPE') == true}" ng-click="types.setType('TYPE')">
<img class="typebox-img" src="">
<div class="typebox">TYPE</div>
</td>
</tr>
where 'type' is the type of table row and 'types' is the angular controller.
types.setType(type):
...
this.types[type] = ! this.types[type];
...
while this toggles values from the second click on, it doesnt change the value on the first click.
I implemented the functionality using the if-else statement, but cant figure out why this wont work as it is a pretty basic thing to do.
this.types[type] is set to false as default.
Could someone explain why is this happening..
It doesn't surprise me that this doesn't work:
ng-click='types.setType('type')
Use " for the inner quotes to make the parser understand what you're trying to do (or the other way around):
ng-click='types.setType("type")'
Incidentally, you don't need a function to do this. Just initialize in your controller a bool:
$scope.toggle = true
And use in your view like this:
ng-click='toggle = !toggle'

Categories

Resources