Count elements in alpine.js - javascript

Please find below a simplified example of what I try to achieve.
I have a big list of which I create an overview using alpinejs.
I now added a filter to just show some of my elements.
I also have a global number of elements shown as well as a global count of stuff the elements have.
In below example I show for each client the cars they own and the number of cars they own. Globally I show the number of clients and the total amount of cars.
This works as long as I do not filter.
As soon as filtering starts, the global numbers act weird.
When started, I have 3 clients and 5 cars.
When I enter an E, It should be 1 client and 2 cars, but it remains as 3 and 5.
When I remove the E, the numbers go up to 8 clients and 12 cars.
I'm sure it is due to the fact that the global counts do not get reset to 0.
But I couldn't find a way to achieve this reset. Any help would be much appreciated.
Please note that I know, I can avoid actually counting the cars with ++individualcount by just replacing x-text="individualcount" with x-text="cars.length".
<html>
<head>
<script defer src="https://unpkg.com/alpinejs#3.x.x/dist/cdn.min.js"></script>
</head>
<body>
<h1 x-data="{ message: 'I ❤️ Alpine' }" x-text="message"></h1>
<div
x-data="{
search: '',
items: [
{client: {name: 'Hugo', id:'0001' }, stuff: {cars: [ 'Horch', 'Benz' ] } },
{client: {name: 'Hans', id:'0002' }, stuff: {cars: [ 'Horch' ] } },
{client: {name: 'Egon', id:'0003' }, stuff: {cars: [ 'VW', 'Maybach' ] } },
],
get filteredItems() {
return this.items.filter(
i => i.client.name.startsWith(this.search)
)
},
}"
>
<input x-model="search" placeholder="Search...">
<ul x-data="{ clientcount : 0, carcount : 0 }">
<p>Number of clients: <span x-text="clientcount"></span></p>
<p>Number of cars: <span x-text="carcount"></span></p>
<template x-for="item in filteredItems" :key="item.client.name">
<li x-data="{ individualcount : 0 }"
x-effect="carcount += individualcount; ++clientcount">
<span x-text="item.client.id"></span>
<span x-text="item.client.name"></span>
(<span x-text="individualcount"></span>)
<ul>
<template x-for="car in item.stuff.cars" :key="car">
<li x-text="car"
x-effect="++individualcount">
</li>
</template>
</ul>
</li>
</template>
</ul>
</div>
</body>
</html>

Related

Bind value from one array of objects based on id from different one using ng-repeat

I have a list of options and then I call an API which validates those options. I plan to display whether an option is valid or not.
I start with this array
$scope.preValidationArray = [
{ id: 1, description: 'Item 1' },
{ id: 2, description: 'Item 2' },
{ id: 3, description: 'Item 3' },
];
and get the following response
$scope.validations = [
{ id: 1, valid: true },
{ id: 2, valid: false },
{ id: 3, valid: false },
];
how can I use ng-repeat to match the right id and NOT rebuild the array again. I would rather just update the valid property on the original array whenever I get a new set of validations. I'm thinking of something like <div ng-bind="validations.valid on option.id">
<ul id="options">
<li ng-repeat="option in preValidationArray track by option.id"
ng-class="{'invalid': !option.valid}">
<div ng-bind="option.description"></div>
//////////////// Here I want to do something like
Valid: <div ng-bind="validations.valid on option.id">
</li>
</ul>
I am going to have a Revalidate button which is going to return the set of validations again so ideally I don't want to rebuild this array over and over again.
Bare in mind I have to trigger a class on <li> element using ng-class if it's invalid.
One simple solution would be using another ng-repeat for the validations:
<ul id="options">
<li ng-repeat="option in preValidationArray track by option.id">
<div ng-bind="option.description"></div>
Valid: <div ng-repeat="validation in validations | filter:{id:option.id} track by validation.id">{{validation.valid}}</div>
</li>
</ul>
Check demo: DEMO
Are your preValidationArray and validations arrays 1:1? Meaning, if pre-validation has 15 elements, does validations as well (and does validations[0] correspond to preValidationArray[0] and so on)? If so you could track on the index and be safe in this instance. With that caveat in mind, you could try something like this:
<ul id="options">
<ng-repeat="option in preValidationArray track by $index">
<ng-bind="option.description"></div>
Valid: <div ng-bind="validations[$index].valid">
</li>
</ul>
Create a function:
$scope.validById = function(id) {
return $scope.validations.find(_ => _.id == id).valid;
};
And use it:
<ul id="options">
<li ng-repeat="option in preValidationArray track by option.id"
ng-class="{'invalid': !validById(option.id)}">
<div ng-bind="option.description"></div>
//////////////// Here I want to do something like
̶V̶a̶l̶i̶d̶:̶ ̶<̶d̶i̶v̶ ̶n̶g̶-̶b̶i̶n̶d̶=̶"̶v̶a̶l̶i̶d̶a̶t̶i̶o̶n̶s̶.̶v̶a̶l̶i̶d̶ ̶o̶n̶ ̶o̶p̶t̶i̶o̶n̶.̶i̶d̶"̶>̶
Valid: <div ng-bind="validById(option.id)"></div>
</li>
</ul>

How to output html from filter inside mustache

I have a input (top right) where users can search things, when it's directive length get 3 characters it will display a list of products and highlight the matches...
Look at my code:
html
<div id="app">
<div id="header">
<div class="right"><input type="text" v-model="message" v-on:keyup="searchStart()" v-on:blur="searchLeave()"/>
<ul v-if="this.searchInput" class="product-list">
<li v-for="product in products">
{{ product.id }} - {{ product.name | highlight }} - {{ product.qtd }}</li></ul>
</div>
</div>
<div id="main">
<div id="menu">fdfds</div>
<div id="container">{{ message }}</div>
</div>
</div>
js
var search = new Vue({
el: "#app",
data: {
message: "",
searchInput: false,
products: [
{
id: 1,
name: "produto 01",
qtd: 20
},
{
id: 2,
name: "produto 02",
qtd: 40
},
{
id: 3,
name: "produto 03",
qtd: 30
},
]
},
methods: {
searchStart: function(){
if(this.message.length >= 3)
this.searchInput = true;
console.log(this.searchInput);
},
searchLeave: function(){
this.searchInput = false;
this.message = "";
console.log(this.searchInput);
}
},
filters: {
highlight: function(value){
return value.replace(search.message, '<span class=\'highlight\'>' + search.message + '</span>');
}
}
});
Here you can see a live pen: http://codepen.io/caiokawasaki/pen/dXaPyj
try to type prod inside the pen...
Is my filter correct? The way I created the filter is correct?
The main question is: How to output the HTML from my filter?
Edit/Solution
The problem in the case was codepen, there is some kind of conflict with vue, so I was not able to escape the html using {{{}}}, put the code in another editor (jsfidle) and it worked.
I'm accepting the answer given to the reward because it's right.
You'll need 3 steps here for achieve what you want:
Use triple braces {{{ }}} to display unescaped html
Filter your users by your v-model variable, in order to just show the matches
Replace the substring matching by the <span> tag
Check out the computed property filteredUsers and the filter in this working jsfiddle

Using AngularJS to get the count of a sub array

Basically I'm making a todo list with angular and I want to display the how many items have been marked as done I have an array object of Lists and each list has a collection of list items called todos like so:
[{listName: "ESSENTIALS", newTodoName:"", newTodoQ:"0", todos: [
{ taskName : "Comb" , quantity: 1, isDone : false , id: "comb" } ]},
{listName: "TOILETRIES", newTodoName:"", newTodoQ:"0", todos: [
{ taskName : "Tooth Brush" , quantity: 1, isDone : false , id: "toothbrush"},
{ taskName : "Tooth paste" , quantity: 6, isDone : false, id: "toothpaste" },
{ taskName : "Deodorant" , quantity: 3, isDone : false, id: "deodorant" }
]}];
So I have two ng-repeats.. One repeats the Lists then another inside of it prints out each list item. I have an H1 and next to the title I want to have the the items that were marked isDone as true next to the total amount of records to show how many items you have left. As you can see I started to code up a filter but I believe it's wrong I keep getting: "Syntax Error: Token 'undefined' is unexpected, expecting [}] at column null of the expression [ (list.todos |] starting at [{4}]." which I'm not really sure what that means...my fourth item is blank? Yet I have all my todos there and they are not blank. Is my filter wrong? or a better way to do this?
<div class='row' ng-repeat="list in lists">
<h1 class='centerTitle'>{{list.listName}} <span class="listCounter"> {{ (list.todos | filter:{todos: {isDone: true}}: true).length }} / {{list.todos.length}}</span></h1>
<ul ui-sortable="todoSortable" ng-model="list.todos">
<li ng-class="{taskDone: todo.isDone}" class="todoTask" ng-repeat="todo in list.todos | orderBy: 'isDone' "></li>
<div>
Can you try:
<h1 class='centerTitle'>{{list.listName}} <span class="listCounter"> {{ (list.todos | filter: {isDone: true}).length }} / {{list.todos.length}}</span></h1>

Incrementing an upvote with ng-repeat

I'm new to angular and am trying to build to learn. I have the following array in a controller. This represents a trade of two players (from two teams).
I think my logic is spot on, why isn't my code working?
$scope.unique = [["Name", "Name", "Name", "Name", {"upvotes": 0}]]
//more can be added to the outer array, in sets of five like this.
In the same controller I have the following function:
$scope.incrementUpvotes = function(value) {
value[4].upvotes++;
};
In my Angular view I am trying to have an ng-repeat, with ng-click incrementing the upvote when this area is clicked. The upvote is working, but it's only being rendered for the first five elements, the ng-repeat doesn't seem to be working.
<div class="text-center" ng-repeat="value in unique">
<span ng-click="incrementUpvotes(value)">
{{value[0]}} and {{value[1]}} for {{value[2]}} and {{value[3]}} upvotes: {{value[4].upvotes}}
</span>
</div>
While you logic is spot on, the error is coming from the fact that your array is multi-dimensional. To fix it as is look at Anik's answer.
But let me suggest a better structure that will make your code much more readable, maintainable and more aligned to JavaScript conventions
$scope.teams = [
{
members: ["Name1", "Name2", "Name3", "Name4"],
upvotes: 0
},
{
members: ["Name5", "Name6", "Name7", "Name8"],
upvotes: 0
}
];
So now your upvote function is more readable like so:
$scope.incrementUpvotes = function(team) {
team.upvotes++;
};
And so is your HTML much cleaner now:
<div class="text-center" ng-repeat="team in teams">
<span ng-click="incrementUpvotes(team)">
{{team.member[0]}} and {{team.member[1]}} for {{team.member[2]}} and {{team.member[3]}} upvotes: {{team.upvotes}}
</span>
</div>
I think your structure is a bit confusing. Try something like this:
JS
$scope.unique = [{team: ["Name", "Name", "Name", "Name"], "upvotes": 0},
{team: ["Name", "Name", "Name", "Name"], "upvotes": 0}]
$scope.incrementUpvotes = function(value) {
value.upvotes++;
};
HTML
<div class="text-center" ng-repeat="value in unique">
<div ng-click="incrementUpvotes(value)">
<span ng-repeat="v in value.team track by $index">
{{v}}
</span>
<span>
upvotes: {{value.upvotes}}
</span>
</div>
</div>
See it working here (click on the row to increment vote): http://www.bootply.com/7LOE1zpE4z

why din't computed don't track changes in this simple knockout app?

The menu is what I want, when mouse over the left, the right should changes but doesn't.
Here is my simplified viewmodel:
var currentSelectIndex = 0;
var AppModel = {
CurrentIndex: ko.observable(currentSelectedIndex),
OnMouseOver: function (data, event) {
// change currentIndex or currentSelectedIndex here
// CurrentSubCategory didn't updated
},
CurrentSubCategory: ko.computed({
read: function() {
return AppModel.Menu[AppModel.CurrentIndex()].subcategory;
},
deferEvaluation: true
}),
Menu: [
{
subcategory: [
{ name: '1', id: 50000436 },
{ name: '2', id: 50010402 },
{ name: '3', id: 50010159 }
],
}
};
And my html:
<div class="categories" id="categories">
<div class="first-category" id="first-category">
<ul data-bind="foreach:Menu">
<li data-bind="text:name,attr:{id:id,class:className},event{ mouseover: $root.myfunction}"></li>
</ul>
</div>
<div class="sub-category" id="sub-category">
<ul data-bind="foreach:CurrentSubCategory()">
<li><a data-bind="text:name,attr:{href:getListUrl(id)}"></a></li>
</ul>
<div class="clear">
</div>
</div>
<div class="clear">
</div>
</div>
Sorry, can't post images due to less than 10 reputation.
Thanks for any help!
There were several syntax errors in your code which I imagine are a result of your making it simpler to post.
I have posted a working jsFiddle here: http://jsfiddle.net/Gy6Gv/2/
I changed Menu to be an observable array only because knockout provides the helper method .indexOf to make it easier to get the index of the menu from the mouseover. Other than that there was no problem with the computed. I imagine there is some other syntactical error in your actual code.

Categories

Resources