Tally Up Number of Occurrences in Angular - html

So basically I have this quiz app im working on using angular and I want to tally up the amount of times the right answer is entered. I already made it so the words 'CORRECT' are displayed by the question if they type the right answer in the text box, but I want to see how many times that happens. Here is my code
div ng-repeat="q in questions">
<span>{{ q.question }}</span><br>
<input type="text" ng-model="q.ans" name="email" placeholder="">
<div ng-show="q.ans===q.answer">CORRECT!</div>
<div>
so basically questions is just an array with a question string and answer string. I want to see at the end how many are correct. So I'm thinking, I added in a correct property to the question objects that has a default of 0 which could mean wrong, and change when its right to 1.
Now how would I make it change from the html page here when someone types the right answer? like if correct is shown, if the ng-show is right, then that value would be 1, if not, it'd be 0.
thanks for any assistance. Wondering if I could do this in real time instead of having a 'check' button at the end.
EDIT: okay I looking around the ng-if directive, would it somehow be possible to add like
<div ng-if="q.ans===q.answer">{{ q.correct = 1 }} </div>
or somehow execute that q.correct = 1 (meaning that answer is correct) if the ng-if block is run?

Make a filter for counting the correct answers
// app is your module
app.filter('correctCount', function() {
return function(questions) {
return questions.reduce(function(count, q) {
return count + (q.ans === q.answer ? 1 : 0);
}, 0);
};
})
Then you can display the total in your template
Total: {{questions | correctCount | number}}
Demo ~ http://plnkr.co/edit/br3fxHQ8q04ajZj6Fxch?p=preview
An alternative to reduce that might be easier to understand is...
return questions.filter(function(q) {
return q.ans === q.answer;
}).length;

Related

Input for currency not treating 0 like other numbers after decimal

I'm make a react application that takes an input that should be shown to two decimal places. When the default input has a one number after the decimal, it should add a second number with a 0 after it. It also lets me add infinite amounts of "0"s, but caps any other number at 2 decimal places.
Two different problems pictured:
Case 1.
This is how the input looks when first loaded. The preferred display would be 1.60 (two decimal places)
Case 2.
Current behavior allows the user to add infinite 0s (any other number it caps at 2 decimal places). Expected behavior should cap all numbers including 0s at 2 decimal places.
function ProductMaterial() {
const [name, setName] = useState("");
function handleChange(e) {
let val = e.target.value;
if (val.includes(".") && val.split(".")[1].length > 2) {
}
else {
setName(parseFloat(val));
setMaterialState(parseFloat(val));
}
}
return (
<div className="input-group-sm col-xs-2 material input-group">
<div className="input-group-prepend">
<span className="input-group-text">$</span>
</div>
<input
className="form-control"
type="number"
min="0.00"
onChange={handleChange}
value={product.material}
/>
</div>
);
}
Here is the code for setMaterialState. It changes the product.material value, and then calls setMaterial() in a useEffect() function. The setMaterial() takes the id of the product, and the new material value and saves it to state.
function setMaterialState(newMaterial) {
product.material = newMaterial;
}
useEffect(() => {
setMaterial(product.id, product.material);
}, [product.material]);
I know that the issue here is that on first load nothing is triggering the onChange() function. I've tried setting value={product.material} to value={product.material.toFixed(2)} but it makes it difficult to fix the input. Is there a good way to initialize it before, maybe using a UseEffect() statement? And on top of this I'm not sure why the 0s can be added indefinitely.
Update:
The first problem with the leading 0s was solved by using the onKeyPress= answer suggested by Photonic.
However, I'm still struggling on the forcing it to be two decimal places when there is a 0 at the end problem. I'm realizing that the issue with not starting doing 1.60 is due to it being converted to a float value and not being a string. I tried doing a UseEffect() [] and force it to go to 2 decimal places with product.material.toFixed(); as well as modify the data to start with being 1.60, but it seems like the only way to force the two decimal places is keeping it as a string or converting it to a string by using product.material.toFixed(2). The value in the input value={product.material} need to be a number to correctly execute calculations and collect user input. I'm not sure what the best solution for this is.
I dont know how you are initializing the first load but you can use UseEffect() to fix the decimal place issue like you mentioned.
For the infinite decimal places...
onChange is an event that is already happen, you might want to try changing from onChange to onKeyDown or onKeyPress and in your code you can add e.preventDefault() to stop number from being entered
if (val.includes(".") && val.split(".")[1].length > 2) {
e.preventDefault()
}
If you want to call a function when the 'component did mount' you can use
useEffect(yourFunction(){}, [])

Rendering div that match array parameters

I have an issue rendering parameters in Angular 8, I get datas from API that I need to render in divs that matchs those datas but I'm having an issue where the datas shows in every divs, here's what it looks like :
Here, "Drops","Misc","Network" are the main divs that need to render the lower-categories.
Altought, what I want is for example, to have only "Drops Aluminium" inside the main "Drops", only "VANNE" inside "Misc" and "Main" in "Network". The lower categories should only renders when they have their ids inside the main id ( see picture 2 below ).
What I have tried :
Binding the values inside the divs, since all main and lower categories have ids like so :
Here is a stackblitz example : https://stackblitz.com/edit/angular-me2ppb?file=src%2Fapp%2Fapp.component.ts
Thank you in advance for your time and help, it's much appreciated !
If I understand correctly, could you just solve it with a nested loop in the template?
<div *ngFor="let main of total_by_level | keyvalue">
{{label_name[main.key]}}
<div *ngFor="let sub of main.value | keyvalue">
{{label_name[sub.key]}}
</div>
</div>
This would result in:
Network
Main
Drops
Drops Aluminium
Misc
VANNE
You have 2 options here:
Adapt your HTML to loop around total_by_level and query label_name appropriately
Build the output in code
It looks like you have attempted both, and so are open to either. Personally, I prefer to do as much as possible in the code and keep the HTML as dumb as possible, so I would take approach 2.
In ngOnInit() (which should be where you do any initial processing), I would build an array based on the structure on total_by_level.
output: any[];
ngOnInit() {
this.output = Object.keys(this.total_by_level).map(levelKey => {
const child = this.total_by_level[levelKey];
return {
level: {
label: this.label_name[levelKey]
},
children: Object.keys(child).map(childKey => ({
label: this.label_name[childKey],
value: child[childKey]
}))
};
});
}
It then becomes simple to bind to this array in your HTML:
<div *ngFor="let item of output">
{{item.level.label}}
<div *ngFor="let child of item.children">
{{child.label}}
{{child.value}}
</div>
</div>
You are dealing with some odd data structures, and I'm not sure of your terminology, so I have guessed a little bit here. You can take the concept of this idea and work with it. I am also assuming that there is only ever 1 nested child in total_by_level.
DEMO: https://stackblitz.com/edit/angular-upqdex

React: How to provide procedurally generated <li> elements distinct HTML id values?

I'm rendering a map of items retrieved from a database and filtered via the value state of an input field and attempting to then set the state of the input field as the value stored in some list item on click. I figured that using document.getElementById().innerHTML would allow me to retrieve the content stored within the appropriate tag and then set it to state which does work, the issue I'm facing is that it will only retrieve the innerHTML of the first item rendered in the map.
I've tried solutions ranging from applying UUID to making the mapped content available to the window and transfering the state of the individual objects but each disparate solution only moves the value of the first item to state - any ideas?
Rendered Content:
window.filteredItems = this.state.items.filter(
(item) => {
return item.companyNameObj.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1;
}
);
<div className="fixed-width">
<div className="search-container">
<form>
<input type="text" name="search" className="search-bar" placeholder="Search: " onChange={this.handleChange} value={this.state.search} />
</form>
<ul className="search-results">
{window.filteredItems.map((item) => {
return (
<div className="distinct-result-container">
<li key={item.id}>
<div className="image-container">
<img src={item.imageObj} alt={item.companyNameObj + " logo."}/>
</div>
<div className="company-container">
<span onClick={this.stateTransfer}><h3 id={"ID"}>{item.companyNameObj}</h3></span>
<p>Owned by: {item.ownerNameObj}</p>
</div>
</li>
</div>
)
})}
</ul>
</div>
<Footer />
</div>
);
stateTransfer()
stateTransfer(id) {
var search = this.state.search;
var uniqueID = document.getElementById("ID").innerHTML;
this.setState({
search: uniqueID
});
}
The current content of stateTransfer() doesn't represent any significant attempts at approaching a solution to this issue, it's just the minimum required implementation to move the innerHTML content to the input fields value.
EDIT: I've further clarified on the task at hand and a potential solution in the comments below (which follow this), I'm just hoping someone is able to help me with the actual implementation.
#DILEEPTHOMAS The list is comprised of data pulled from a Firebase Realtime Database and is rendered via mapping the filteredList and a search query; that functoionality works fine - what I need is to be able to click the element of any distinct li and have the innerHTML (the text stored in that li's item.companyNameObj) be moved to the value of the input field (so users can navigate the search content with re-typing).
#JoshuaLink I can't necessarily configure the items of the list any
further as it's just data pulled from an external database - I believe
the appropriate solution is to somehow provide a unique HTML ID value
to each newly rendered li and have that selected ID moved to
stateTransfer() where it can be set as the input fields value, I'm
just struggling with the actual implementation of this.
EDIT 2: I've managed to figure out a solution to both parts of the problem as described above - I'll post it as an answer below.
I managed to solve both parts of my problem:
The key issue, which was moving the text stored in each distinct li to the input value, which was apparently easily solved by making my stateTransfer() function accept an event and passing the .innerText value of the h3 through the event (I assumed I would have to use .innerHTML, which would require me to provide each distinct li with a unique generated ID) as follows:
stateTransfer(e) {
var search = this.state.search;
var innerText = e.target.innerText
this.setState({
search: innerText
})
}
The secondary issue, (which I incorrectly assumed was integral to implementing a solution to my question), assigning unique HTML id values to my procedurally generated li's was solved by implementing a for-loop in a componentDidUpdate() function which iterates through the current total length of the list and and assigns an id with the loop iterator concatenated to the end of the string as follows:
componentDidUpdate() {
var i;
var searchCompanyNames = document.querySelectorAll('.comapnyNames');
for(i = 0; i < searchCompanyNames.length; i++) {
searchCompanyNames[i].id = 'companyName-' + i;
}
}
Whilst I didn't need to assign unique ID's to the li's in the correct implementation, it's a useful trick worth noting nonetheless.

stop ng-repeat when match ng-if

I have an object person like this:
person:
phone:
phone1:
number:"xxx-xxx-xxxx"
checkedSms:true/false
phone2:
number:"xxx-xxx-xxxx"
checkedSms:true/false
smsNumber:""
I have a link <a> I only want to display it under certain condition: either one of the phones checkedSms is true(person.phone.phone[i].checkedSms = true) or smsNumber.length > 0.
I can do the second part by this:
<a ng-click="..." ng-if="person.smsNumber.length > 0"
<div>
...
</div>
</a>
But I don't know how to add the first part, and combine it with the second part above. Tried ng-repeat to repeatly checking on phones of person, but multiple matching results in multiple <a> display. The link should only show once, whenever one condition matches, it should stop further checking.
Hope somebody can help me out. Thanks a lot.
This is how I would do it.
In the HTML:
<a ng-if="checkForSms()" ng-click="...">
In the controller:
$scope.checkForSms = function() {
// code that returns true if the conditions are met
}
Also, I would start using "controllerAs" syntax to prepare for Angular 2.0. https://www.airpair.com/angularjs/posts/preparing-for-the-future-of-angularjs#3-2-remove-dependencies-on-scope
I would create a property in the controller, call it filterPersonList
$scope.filterPersonList = $scope.personList.filter(function (e) {
return e.smsNumber.length > 0 || whatever other condition
})
Then use ng-repeat with the filterPersonList

Handling checkboxes and getting values

I'm pretty new to MVC and I'm having a hard understanding how to get the values (basically the IDs) to checkboxes that I'm generating. Here are my checkboxes:
<div id='myCheckboxDiv'>
<input type="checkbox" onclick="checkAll(this)">Check All
#foreach (var form in #Model.DetailObject.DoaFormGroupDocuments)
{
<br>
var checkBoxId = "chk" + form.DocumentId;
#Html.CheckBox(checkBoxId, new { value = form.DocumentId, #checked = true });
#form.DocumentName;
}
</div>
Essentially what I want to do is get the ID to which ever checkbox is checked and save it in to a list after I click a save button at the bottom of the page.
I have run across something like this to handle everything but I'm not quite sure how to use it really...
var values = $('#myCheckboxDiv').find('input:checkbox:checked').map(function () {
// get the name ..
var nameOfSelectedItem = this.attr('name');
// skip the ‘chk’ part and give me the rest
return nameOfSelectedItem.substr(3);
}).get();
The only thing you need to think about is the value of the name attribute your checkbox(es) will have. The way you're handling it right now, your post body is going to have a fairly randomized collection of chkN-named parameters, where N is some number. The modelbinder will need something similarly named as a parameter to your action method in order to bind the posted values to something useful. That's a tall order for something that will be some what variable (the DocumentId values).
The best option would be to set up your checkboxes, instead, as a collection, which means giving them names chk[0], chk[1], etc. Then in your action you can accept a parameter like List<string> chk, and that will contain a list of all the values that were posted.