I have following code: my code
I need to check if my elements name.length sum count is greater than 25 then only show items whose name.length sum is less than 25. And also print count of items which are not shown.
I am trying something like this, but I stuck, please help :)
const skills = [
{
id: 1,
name: "Html"
},
{
id: 2,
name: "css"
},
{
id: 3,
name: "bootstrap 4"
},
{
id: 4,
name: "scss"
},
{
id: 5,
name: "JS"
},
{
id: 6,
name: "React"
},
{
id: 7,
name: "Jquery"
},
{
id: 8,
name: "Vue JS"
}
];
return (
<div>
{skills.map((skill) => (
<li key={skill.id}>{skill.name}</li>
))}
{/* with this line I am wanting to show each elemet char count */}
{skills.map((skill) => (
<div>{skill.name.length}</div>
))}
{/* need to show hide elements count */}
<div>+{count}</div>
</div>
);
In the end I need to get view like this
Html
css
bootstrap 4
scss
JS
+3
Well you can approach this as:
let lengthCount = 0;
let maxIndex = 0;
skills.map((item, index)=>{
if(lengthCount <= 25 ){
maxIndex = index;
console.log("item: " , item.name)
}
lengthCount = lengthCount + item.name.length;
})
console.log("+",skills.length - maxIndex, " more");
Result:
Here is the link to codepen https://codepen.io/the_only_yasir/pen/dyOdZZR?editors=0010
You can do it like this.
return (
<div>
{skills.map((skill) => {
if (skill.name.length < 25) {
return <li key={skill.id}>{skill.name}</li>;
} else {
countMoreThan25++;
}
})}
+{countMoreThan25}
</div>
);
Here is the code for reference.
https://codesandbox.io/s/jolly-newton-qjxpw?file=/src/App.js:549-799
Related
I'm doing an React assignment for school but I'm a bit stuck and I can't find the right answer.
I have a data file with the following data:
const students = [
{
"name": "Evelyn",
"assignment": "SCRUM",
"difficultyRating": 3,
"funRating": 4
},
{
"name": "Evelyn",
"assignment": "W1D1-1",
"difficultyRating": 3,
"funRating": 3
},
{
"name": "Evelyn",
"assignment": "W1D2-1",
"difficultyRating": 1,
"funRating": 3
}
]
This goes on, there are 10 student and different assignments. What I need to do is get the average of the difficultyRating and funRating per assignment and use this data in a Victory Graph to display. Victory is working but it's not displaying the average.
I have this function already which takes all the assignments and fills it with the other data but I don't know what to do in the next step, how can I make a new Array of objects that I can use in the Victory Chart which displays the average difficulty/funrating per assignment.
The code I have so far is this:
const groupByAssignment = (objectArray, property) => {
return objectArray.reduce(function (total, obj) {
let key = obj[property];
if (!total[key]) {
total[key] = [];
}
total[key].push(obj);
return total;
}, {});
}
let groupedAssignments = groupByAssignment(students, 'assignment');
In the Victory Graph the output looks like this now:
<VictoryBar
style={{
data: {
fill: "#ff0b03",
},
}}
barWidth={2}
data={props.data}
x="assignment"
y="difficultyRating"
/>
)}
What I need is a data piece that has every assignment with the difficulty/rating averaged from all 10 students.
The following will give the average values of fun rating and difficulty rating. I have added more entries as there was only one record for each assignment.
const students = [ { name: "Evelyn", assignment: "SCRUM", difficultyRating: 3, funRating: 4, }, { name: "Pqr", assignment: "SCRUM", difficultyRating: 4, funRating: 2, }, { name: "Evelyn", assignment: "W1D1-1", difficultyRating: 3, funRating: 3, }, { name: "Evelyn", assignment: "W1D2-1", difficultyRating: 1, funRating: 3, }, { name: "Abc", assignment: "W1D2-1", difficultyRating: 5, funRating: 4, }, ];
const groupByAssignmentWithAverage = (objectArray, property) => {
return objectArray.reduce(
(prevValue, { difficultyRating, funRating, [property]: key }) => {
// key is the value of in obj with property as data name
if (!prevValue[key]) {
// create a new entry for each assignment type
prevValue[key] = {
difficultyRatingAvg: difficultyRating,
funRatingAvg: funRating,
count: 1,
};
} else {
// get the previous count and average values
const {count, difficultyRatingAvg, funRatingAvg} = prevValue[key];
prevValue[key] = {
difficultyRatingAvg:
(difficultyRatingAvg + difficultyRating) /
(count + 1),
funRatingAvg: (funRatingAvg + funRating) / (count + 1),
count: count + 1,
};
}
return prevValue;
},
{}
);
};
let output = groupByAssignmentWithAverage(students, "assignment");
console.log(output);
My solution would be to seprate the objects with same assignment name, and then just find the average of that particular assignment by mapping over individual arrays of objects which we get from initial step.
result = students.reduce(function (r, a) {
r[a.assignment] = r[a.assignment] || [];
r[a.assignment].push(a);
return r;
}, Object.create(null));
console.log(result);
Now you can easily format data according to the graph package you are using.
I need to iterate over shoppingItem (items) inside shoppingOrder (orders). There are three orders. The first order has one item (itemid:1), second order has six items (itemid:2,3,4,5,6,7), third order has one item (itemid:8). I need to show only the top five items. i.e., 1,2,3,4,5 but the current code only limits the item in the second order, showing five items inside. The final output comes like 1,2,3,4,5,6,8
If first order has five items the loop should exit, if first order has one item and second order has six items, it has to show 1 in order 1 and 2,3,4,5 in order2 and exit before order3. But in my example, using order.shoppingItem.slice(0,5) only limits the items in the second order. It is not limiting the total items. How do I resolve this issue? I am using Vue JS version 2
NestedLoop.vue
<template>
<div>
<div v-for="order in shoppingOrder" :key="order.orderId">
<div v-for="item in order.shoppingItem.slice(0,5)" :key="item.itemId">
{{item.itemId}}
</div>
</div>
</div>
</template>
<script>
export default {
name: "NestedLoop",
data() {
return {
shoppingOrder: [
{
orderId: 1,
orderDate: "7/30/2020",
orderStatus: "Dispatched",
shoppingItem: [
{
itemId: 1,
itemName: "Pen",
itemQuantity: "1",
itemPrice: "10.00"
}
]
},
{
orderId: 2,
orderDate: "7/25/2020",
orderStatus: "Ordered",
shoppingItem: [
{
itemId: 2,
itemName: "Notebook",
itemQuantity: "2",
itemPrice: "40.00"
},
{
itemId: 3,
itemName: "Scale",
itemQuantity: "3",
itemPrice: "100.00"
},
{
itemId: 4,
itemName: "Sharpener",
itemQuantity: "1",
itemPrice: "10.00"
},
{
itemId: 5,
itemName: "DocumentFolder",
itemQuantity: "1",
itemPrice: "10.00"
},
{
itemId: 6,
itemName: "PencilBox",
itemQuantity: "5",
itemPrice: "140.00"
},
{
itemId: 7,
itemName: "SketchBox",
itemQuantity: "5",
itemPrice: "10.00"
}
]
},
{
orderId: 3,
orderDate: "7/34/2020",
orderStatus: "Dispatched",
shoppingItem: [
{
itemId: 8,
itemName: "Sketch",
itemQuantity: "1",
itemPrice: "10.00"
}
]
}
]
};
},
methods: {}
};
</script>
<style scoped>
</style>
Result
1
2
3
4
5
6
8
Expected only to return top 5 items like this (1 in order1 and then 2,3,4,5 in order2),
1
2
3
4
5
I love how succinct #Gaetan C's answer is, but .flat is a relatively new method that will require poly-filling for browsers if you are using Vue-cli 3. See this Github issue for more details. If you run into any challenges, you may need to manually edit the poly-fill option of the babel preset.
For more compatibility among other browsers or if you are using vue cli 2 or below, I present this solution.
computed: {
firstFiveShoppingItems: {
let initialShippingItem = [];
for (const order of this.shoppingOrder) {
if (initialShippingItem.length + order.shoppingItem.length >=5) {
return [...initialShippingItem, ...order.shoppingItem].slice(0, 5);
}
initialShippingItem = [...initialShippingItem, ...order.shoppingItem]
}
return initialShippingItem;
}
}
Then in your template, you add
<div v-for="item in firstFiveShoppingItems" :key="item.itemId">
{{item.itemId}}
</div>
As I said, it's not as elegant or succinct as #Gaetan C's answer but it will be more compatible among other browsers and you won't need to go through headaches with poly-fill configurations.
Another thing I like about my solution is that it doesn't iterate over all shopping item arrays like #Gaetan C's answer. It breaks when the result is obtained.
Just use a computed property instead to get the 5 first shopping items:
computed: {
firstShoppingItems: {
return this.shoppingOrder.map(x => x.shoppingItem).flat().slice(0, 5);
}
}
Then you just need one v-for loop:
<div v-for="item in firstShoppingItems" :key="item.itemId">
{{item.itemId}}
</div>
I'd recommend taking advantage of Vue's computed properties to make a list limited to five items. Currently your code is only slicing the inner list to five items.
computed: {
topFive() {
const limit = 5;
let count = 0;
let res = []
for (let i = 0; i < this.shoppingOrder.length && count < limit; i += 1) {
let order = this.shoppingOrder[i];
for (let j = 0; j < order.shoppingItem.length && count < limit; j += 1) {
let item = order.shoppingItem[j];
res.push(item);
count++;
}
}
return res;
}
Once you have a computed property, you can call it from the template like so:
<div v-for="item in topFive" :key="item.itemId">{{item.itemId}}</div>
I need to calculate the total for a specific field inside map
{agent.reviews.map((a, i) => {
return (
<p style={{fontSize: 15}} key={a.id}>Knowledge Rating:
{a.local_knowledge_rating}</p>
)
})}
Returns
Knowledge Rating: 1
Knowledge Rating: 2
But I need to achieved is to get the total instead
Knowledge Rating: 3
Any ideas?
"users": [
{
"name": "someee",
"reviews": [
{
"id": 1,
"local_knowledge_rating": 1,
},
{
"id": 2,
"local_knowledge_rating": 2,
}
],
}
]
You can do something like below
Declare below method outside render and pass reviews array to it as param
The below approach will sum the rating for every user
sumRating = reviews => {
let total = 0;
agent.reviews.forEach(a => {
total+= a.local_knowledge_rating;
});
return total;
}
<p style={{fontSize: 15}}>Knowledge Rating: {this.sumRating()}</p>
There are many other ways to implement the same. The above approach is one of them
you can try sumBy with lodash: https://lodash.com/docs/4.17.11#sumBy
renderKnowledgeRating() {
const sum = agent.reviews.reduce(function(a, b) { return a. local_knowledge_rating + b. local_knowledge_rating; }, 0)
return (
<p style={{fontSize: 15}}>Knowledge Rating: {sum}</p>
)
}
{this.renderKnowledgeRating()}
To enhance readability, extract the portion out to another method renderKnowledgeRating. and instead of using map which not really helping in your situation, handle the calculation differently, and return a single component
As suggested try this:
function getSum(total, num) {
return total + num.local_knowledge_rating;
}
{agent.reviews.map((a, i) => {
return (
<p style={{fontSize: 15}} key={a.id}>Knowledge Rating:
{a.reduce(getSum)}</p>
)
})}
It's a small memory game: the player has to remember a list of words and then he is presented with another list which contains few words from the original list where he has to decide if any of these words were present in the original list. For that he can click on 'Yes' or 'No' radio button.
<div align="center" ng-show="showWords" style="margin-bottom:1cm;">
<div ng-repeat="word in words">
<h4>{{word}}</h4>
</div>
</div>
<div align="center" ng-show="showTest" style="margin-bottom:1cm;">
<div ng-repeat="word in wordsPresent | limitTo:limitTo">
<h4>{{word}}</h4>
<label>
<input type="radio" value="yes">
Yes
</label><br/>
<label>
<input type="radio" ng-value="no">
No
</label><br/>
</div>
</div>
...
<div align="center" ng-if="showOk">
<button class="button button-dark" ng-click="pushToArray()">OK</button>
</div>
In my controller I have:
$scope.words=['Okra', 'Musa', 'Sky', 'India', 'Rose', 'Titanic', 'Onion', 'Germany', 'Beer', 'Run', 'Garden', 'Mountain']
$scope.wordsPresent=['King', 'Garden', 'America', 'Sky', 'Potato', 'Germany', 'Rose', 'Whisky', 'Mumbai', 'Walk']
$scope.playerChoices=[]
The first array is the original array against which I have to check, second array is what I am presenting to the user right now, and third array might be the array where I push his choices.
It looks like following:
How can I implement this?
Update
Please see this updated plunker demo.
To populate the data dynamically onto the DOM, you can use ng-repeat. Please see the code on plunker.
<span ng-repeat="item in someArray">{{item}}</span>
To interpolate the looped item, use {{ }}.
Important : Just please avoid duplicate data on $scope.wordsPresent array coz it will produce error on ng-repeat.
Instead of pushing the "choices" at the end of the quiz/survey(?) why not push it every time the radio changes it's value? Push to array if yes is selected and remove from array if no is selected.
In that case you can use ng-change event to trigger the pushing and removing of data in the array, on the controller. You should take advantage of the 2 way data binding angularjs is offering.
You can then cross check your arrays.
Please see this plunker demo I created and I hope it will help you.
If you can make do with a library, I would suggest lodash.
_.intersection([2, 1], [2, 3]);
// ➜ [2]
Or in the context of your problem:
_.intersection($scope.words, [$scope.wordsPresent]);
// ➜ ['Sky', 'Germany', 'Rose', 'Garden']
https://lodash.com/docs#intersection
It seems to me if you want to check for correct answers, you need to do more than just compare arrays. Suppose they select "No" when something is actually in the list. First, I would put a key field on the items in my array so you are not so limited. Here is a working Plunker
Your data should probably look something like this:
$scope.words = [{
id: 11,
name: 'Okra'
}, {
id: 12,
name: 'Musa'
}, {
id: 4,
name: 'Sky'
}, {
id: 13,
name: 'India'
}, {
id: 14,
name: 'Rose'
}, {
id: 15,
name: 'Titanic'
}, {
id: 16,
name: 'Onion'
}, {
id: 6,
name: 'Germany'
}, {
id: 17,
name: 'Beer'
}, {
id: 18,
name: 'Run'
}, {
id: 2,
name: 'Garden'
}, {
id: 19,
name: 'Mountain'
}]
$scope.wordsPresent = [{
id: 1,
name:'King'
},
{
id: 2,
name: 'Garden'
}, {
id: 3,
name: 'America'
}, {
id: 4,
name: 'Sky'
}, {
id: 5,
name: 'Potato'
}, {
id: 6,
name: 'Germany'
}, {
id: 7,
name: 'Rose'
}, {
id: 8,
name: 'Whisky'
}, {
id: 9,
name: 'Mumbai'
}, {
id: 10,
name: 'Walk'
}]
Then I would add an answer field (you could include it when you set up your data or you can include i after like this:
setAnswers();
function setAnswers() {
angular.forEach($scope.wordsPresent, function (value, key) {
$scope.wordsPresent[key].answer = 'no';
});
angular.forEach($scope.words, function (value, key) {
$scope.words[key].answer = 'yes';
})
}
Your HTML could look like this:
<div ng-app="myModule" ng-controller="HomeCtrl">
<div align="center" style="margin-bottom:1cm;">
<div ng-repeat="word in wordsPresent | limitTo:limitTo">
<h4>{{word.name}}</h4>
<input type="radio" ng-model="word.answer" value="yes">
Yes
<br />
<input type="radio" ng-model="word.answer" value="no">
No
<br />
</div>
</div>
<div ng-show="showAnswers">
<span>Correct Answers: {{correct}}</span> <span>Incorrect Answers: {{wrong}}</span>
You selected:
<div ng-repeat="a in playerChoices">
<span>{{a.name}}</span> <span>{{a.answer}}</span>
</div>
</div>
<div align="center">
<button class="button button-dark" ng-click="pushToArray()">OK</button>
</div>
</div>
And in your controller:
$scope.pushToArray = function () {
$scope.correct = 0;
for (var i = 0; i < $scope.wordsPresent.length; i++) {
$scope.playerChoices.push($scope.wordsPresent[i]);
}
$scope.showAnswers = true;
$scope.correct = correctAnswers($scope.playerChoices, $scope.words);
}
function correctAnswers(checkerArray, targetArray) {
correct = 0;
wrong = 0;
for (var i = 0; i < checkerArray.length; i++) {
if (checkerArray[i].answer == 'yes') {
if (containsObject(checkerArray[i], targetArray) == true) {
correct = correct + 1;
}
}
else if (checkerArray[i].answer = 'no') {
if (containsObject(checkerArray[i], targetArray) == false) {
correct = correct + 1;
}
}
}
return correct;
}
function containsObject(obj, list) {
var i;
for (i = 0; i < list.length; i++) {
if (list[i].id === obj.id) {
return true;
}
}
return false;
}
Happy coding!
I need filter for ng-repeat, that explode elements in "general" array, if element exist in "suggest" array (by id field).
$scope.general= [{id: 21323, name: 'alex'}, {id: 8787, name: 'maria'}, {id: 8787, name: 'artem'}];
$scope.suggest = [{id: 21323, name: 'alex'}, {id: 8787, name: 'maria'}];
<div ng-repeat="elem in general">{{elem.name}}</div>
You should create your own custom filter and you'll probably want to use Array.prototype.filter.
You said you wanted to exclude by the property id. The following filler optionally allows specifying a property. If the property is not specified, then the objects are excluded by strict equality (the same method used by the ===, or triple-equals, operator) of the objects.
angular.module('myFilters', [])
.filter('exclude', function() {
return function(input, exclude, prop) {
if (!angular.isArray(input))
return input;
if (!angular.isArray(exclude))
exclude = [];
if (prop) {
exclude = exclude.map(function byProp(item) {
return item[prop];
});
}
return input.filter(function byExclude(item) {
return exclude.indexOf(prop ? item[prop] : item) === -1;
});
};
});
To use this filter in your html:
<div ng-repeat="elem in general | exclude:suggest:'id'">{{elem.name}}</div>
Here is an example jsfiddle:
https://jsfiddle.net/6ov1sjfb/
Note that in your question artem's id matches maria's thus both artem and maria were filtered. I changed artem's id in the plunker to be unique to show that the filter works.
Try this :
var myApp = angular.module('myApp', []);
function MyCtrl($scope) {
$scope.general = [{
id: 21323,
name: 'alex'
}, {
id: 8787,
name: 'maria'
}, {
id: 8787,
name: 'artem'
}];
$scope.suggest = [{
id: 21323,
name: 'alex'
}, {
id: 8787,
name: 'maria'
}];
$scope.filteredArray = function () {
return $scope.general.filter(function (letter) {
for (i = 0; i < $scope.suggest.length; i++) {
return $scope.suggest[i].id !== letter.id
}
});
};
}
and
<div ng-repeat="elem in filteredArray(letters)">{{elem.name}}</div>
check out the fiddle : http://jsfiddle.net/o2er6msv/
Note: please chk ur id, they are duplicated