How do I create dynamic rows in Vue.js that behave independently? - javascript

I have a vue instance that creates dynamic rows. The row has two Select Options:
Person and Interests with an Add row button. These Select Options are dependent options:
If I select Brian as the Person, his Interests in the second Select Options should load Brian's interests. This works well:
Problem:
This works fine, however the strange thing that I cannot overcome is when a second row is added, the second-row deletes Brian's Interests and Populates the second persons interests:
Brian isn't interested in Crossfit for instance - these are Candice's options. In essence, Brian's options are not preserved, they are overwritten with Candices - so both rows show Candices interests.
Can anyone advise on what I need to do to correct the problem? I have created a Jsfiddle to illustrate the issue:
https://jsfiddle.net/syed263/y9emdLvr/45/
HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<div id="app">
<ul>
<li v-for="(input, index) in inputs">
Person:
<select type="text" v-model="input.one" v-on:change="updateList(input.one)">
<option v-for= "options in person._person" v-bind:value ="options">
{{ options.name }}
</option>
</select> Interests:
<select type="text" v-model="input.two">
<option v-for= "options in activity._activity" v-bind:value ="options">
{{ options.name }}
</option>
</select>
<button #click="deleteRow(index)">Delete</button>
</li>
</ul>
<button #click="addRow">Add row</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
</body>
</html>
JS: declaration
let _people = {
_person: [{
name: 'Adam',
age: '16',
ID: '2009121',
},
{
name: 'Brian',
age: '18',
ID: '2009122',
},
{
name: 'Candice',
age: '16',
ID: '2009120',
},
]
}
let _interests = {
_activity: [{
name: '',
type: '',
}, ]
}
let person
JS Methods:
const app = new Vue({
el: '#app',
data: {
inputs: [],
person: _people,
activity: _interests,
},
methods: {
addRow() {
this.inputs.push({
one: '',
two: ''
})
},
deleteRow(index) {
this.inputs.splice(index, 1)
},
updateList(val) {
this.activity._activity = [];
if (val.name == "Adam") {
this.activity._activity.push({
name: 'Badminton',
type: '20'
}, {
name: 'Football',
type: '30'
})
} else if (val.name == "Brian") {
this.activity._activity.push({
name: 'Basketball',
type: '90'
}, {
name: 'Karate',
type: '50'
})
} else if (val.name == "Candice") {
this.activity._activity.push({
name: 'Climbing',
type: '90'
}, {
name: 'Cross Fit',
type: '100'
})
}
}
}
})
JSfiddle: https://jsfiddle.net/syed263/y9emdLvr/45/

The problem is that you use this.activity._activity(every row use the same) to generate the <option>
But in updateList() whenever the first select value change , no matter which row,
it will change this.activity._activity.
And it will affect all second select options, every row.
So you should do something to link activity to each row.
Sample code like below, it works, but not perfect.
let _people = {
_person: [{
name: 'Adam',
age: '16',
ID: '2009121',
},
{
name: 'Brian',
age: '18',
ID: '2009122',
},
{
name: 'Candice',
age: '16',
ID: '2009120',
},
]
}
let _interests = {
_activity: [{
name: '',
type: '',
}, ]
}
let person
const app = new Vue({
el: '#app',
data: {
inputs: [],
person: _people,
activity: _interests,
},
methods: {
addRow() {
this.inputs.push({
one: '',
two: '',
activity: [] // To link activity with each row
})
},
deleteRow(index) {
this.inputs.splice(index, 1)
},
updateList(val, index) {
this.activity._activity = [];
if (val.name == "Adam") {
// only change current row's second option
this.inputs[index].activity =[{
name: 'Badminton',
type: '20'
}, {
name: 'Football',
type: '30'
}]
} else if (val.name == "Brian") {
this.inputs[index].activity =[{
name: 'Basketball',
type: '90'
}, {
name: 'Karate',
type: '50'
}]
} else if (val.name == "Candice") {
this.inputs[index].activity =[{
name: 'Climbing',
type: '90'
}, {
name: 'Cross Fit',
type: '100'
}]
}
}
}
})
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<div id="app">
<ul>
<li v-for="(input, index) in inputs">
Person:
<select type="text" v-model="input.one" v-on:change="updateList(input.one, index)">
<option v-for= "options in person._person" v-bind:value ="options">
{{ options.name }}
</option>
</select> Interests:
<select type="text" v-model="input.two">
<option v-for= "options in input.activity" v-bind:value ="options">
{{ options.name }}
</option>
</select>
<button #click="deleteRow(index)">Delete</button>
</li>
</ul>
<button #click="addRow">Add row</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
</body>
</html>

Related

VueJs How to make dropdown component to accept different types of props

I want to create a dynamic dropdown component where the amount of dropdowns and their options would be dynamic based on the props provided.
I would like to call the dropdown component and pass an object with the names of individual selects. I want the number of select to be determined by the names provided.
selectNameUsers: { users: "Users", roles: "Roles" } - this should create two dropdowns with the name of users and roles. - THIS HAS BEEN DONE.
Now my question/issue. How can I pass the data for the options. My attempt kinda works but the data provided is:
a) duplicated across multiple selects
b) I had to hard code the v-for= for the data provided so the component is not truly dynamic.
Any ideas?
Code:
https://codesandbox.io/s/adoring-lewin-2pqlq?file=/src/components/parent.vue:0-1245
App.vue
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<parent />
</template>
<script>
import parent from "./components/parent.vue";
export default {
name: "App",
components: {
parent: parent,
},
};
</script>
parent.vue
<template>
<div class="first">
<custom-dropdown
title="Users Manager"
instructions="Assign Role to User"
:selectName="selectNameUsers"
:users="users"
:roles="roles"
>
</custom-dropdown>
</div>
<div class="second">
<custom-dropdown
title="Cars Manager"
instructions="Look at cars"
:selectName="selectNameCars"
:cars="cars"
>
</custom-dropdown>
</div>
</template>
<script>
import customDropdown from "./customDropdown.vue";
export default {
components: { customDropdown },
data() {
return {
selectNameUsers: { users: "Users", roles: "Roles" },
selectNameCars: { cars: "Cars" },
users: [
{ uid: 1, name: "Ade", role: "standard" },
{ uid: 2, name: "Bab", role: "admin" },
{ uid: 3, name: "Cad", role: "super_admin" },
],
roles: [
{ rid: 1, name: "standard" },
{ rid: 2, name: "admin" },
{ rid: 3, name: "super_admin" },
],
cars: [
{ cid: 1, type: "Audi", colour: "Red" },
{ cid: 2, type: "BMW", colour: "Black" },
],
};
},
};
</script>
customDropdown
<template>
<div>
<h1>{{ title }}</h1>
<select
v-for="(objectvalue, keyName, index) in selectName"
:key="index"
:name="keyName"
>
<option v-for="user in users" :key="user.uid" :value="user.uid">
{{ user.name }}
</option>
<option v-for="role in roles" :key="role.rid" :value="role.uid">
{{ role.name }}
</option>
</select>
<h2>{{ instructions }}</h2>
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
},
instructions: {
type: String,
},
selectName: {
type: Object,
},
users: {
type: Object,
},
roles: {
type: Object,
},
},
data() {
return {};
},
};
</script>

Combine the same attribute from different arrays in href (vue.js)

I’m new to vue.js. I’m building a wizard, but one step I can’t figure out.
I’ve got a checkbox list that outputs the list a user has choosen. So far so good.
In the same array there is an urlAnchor that needs to be combined in the final url.
So for instance, if the user selects extra1 and extra2, the list will be:
List view
Product: Extra 1
Price: 129
URL Anchor: /iii
Product: Extra 2
Price: 49
URL Anchor: /jjj
URL
URL needs to be google.com/iii/jjj
But I don’t know how to combine the 2 url anchors in 1 url.
Can someone help me with this please?
My code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test</title>
</head>
<body>
<!-- wizard -->
<div id="app">
<div v-for="(extraItem, index) in extra" v-bind:id="'extraItem-'+extraItem.id" :class="'w-1/2 overflow-hidden my-1 px-1 item-'+index" >
<input type="checkbox" class="extraCheckboxes" :id="extraItem.name" :value="extraItem.name" v-model="checkedExtras" #click="saveExtra(index)">
<label class="form-check-label" :for="extraItem.id">{{extraItem.name}}</label>
</div>
<h1>Output:</h1>
<h2> List view </h2>
<div v-for="extra in extraOutput">
<strong>Product:</strong> {{extra.name}} <br />
<strong>Price:</strong> <span class="items" :data-value="extra.price">{{extra.price}}</span><br />
URL Anchor: {{extra.urlAnchor}}
<p> </p>
</div>
<h2> URL </h2>
<button><a v-for="extra in extraOutput" :href="'https://google.be'+extra.urlAnchor">Button text</a></button>
</div>
<!-- scripts -->
<script src='https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js'></script>
<script type="text/javascript">
function extras() {
return [
{ id: 1, name: "Extra 1", price: 129, urlAnchor:"/iii" , selected: false },
{ id: 2, name: "Extra 2", price: 49, urlAnchor:"/jjj" , selected: false },
{ id: 3, name: "Extra 3", price: 59, urlAnchor:"/ggg" , selected: false },
{ id: 4, name: "Extra 4", price: 69, urlAnchor:"/hhh" , selected: false }];
}
new Vue({
el: "#app",
data() {
return {
extra: extras(),
checkedExtras: [],
data: [],
};
},
methods: {
saveExtra: function (index) {
this.extra[index].selected = !this.extra[index].selected;
}
},
computed: {
extraOutput: function () {
let extra = this.extra.filter(function (item) {
return item.selected === true;
});
return extra;
}
}
});
</script>
</body>
</html>
You've already done most of the work with by writing the extraOutput computed.
computed: {
extraOutput() {
const anchorsOfSelectedExtras = this.extra
.filter(extra => extra.selected) //get array of only selected
.map(extra => extra.urlAnchor); //turn array of objects into array of strings
return anchorsOfSelectedExtras.join(''); //turn array of strings into one joined string
}
}
You can also go even shorter:
computed: {
extraOutput() {
return this.extra.reduce((accumulator, extra) => extra.selected ? [...accumulator, extra.urlAnchor] : accumulator, []).join('');
}
}

List View Search Filter using vue JS

I want to search by header and content. for now can only be based on content. any suggestions or can add from this source code. Thank you
Here is my HTML
<div id="list">
<input type="text" v-model="search">
<ol>
<li v-for="(items, key) in groupedItems">
<h3>{{ key }}</h3>
<p v-for="todo in items">{{ todo.name }}</p>
</li>
</ol>
</div>
here is preview code in js fiddle: https://jsfiddle.net/60jtkp30/9/
It may not be the cleanest solution, but based on your implementation in the jdfiddle, just changing the filter function would be enough (I think).
var list = new Vue({
el: '#list',
data: {
search: '',
items: [
{ name: 'mike', type: 'student' },
{ name: 'beckham john', type: 'footballer' },
{ name: 'walcott', type: 'footballer' },
{ name: 'cech', type: 'footballer' },
{ name: 'jordan', type: 'actor' },
{ name: 'tom', type: 'actor' },
{ name: 'john', type: 'actor' }
]
},
computed: {
groupedItems() {
const arr = {}
//fungsi search
var searchResult = this.items.filter( todo => {
return todo.name.toLowerCase().indexOf(this.search.toLowerCase())>-1 || todo.type.toLowerCase().indexOf(this.search.toLowerCase())>-1;
} )
//grouping
for(var i = 0; i < searchResult.length; i++) {
const key = searchResult[i].type
if (arr[key]) {
arr[key].push(searchResult[i])
} else {
arr[key] = [searchResult[i]]
}
}
return arr
}
}
})

filter with dropdown on click event using angular

I am just trying to filter or search for the relavent data using the input from the dropdown . the requirement is select an option in the dropdown and click on the button it should filter or populate the respective data into the table using angular. i tried i am able to do it directly but not with the click event . Please help me to find out the solution for this as i am kind of new to angular. Here is my code:
My Html:
Filter:
<select ng-model="filterItem.store" ng-options="item.name for item in filterOptions.stores">
</select>
<button >search</button>
<table>
<tr>
<th>Name</th>
<th>Price</th>
<th>Rating</th>
</tr>
<tr ng-repeat="item in data | filter:customFilter">
<td ng-click="">
{{item.name}}</td>
<td>{{item.price}}</td>
<td>{{item.rating}}</td>
</tr>
</table>
JS File:
$scope.customFilter = function (data) {
if (data.rating === $scope.filterItem.store.rating) {
return true;
} else if ($scope.filterItem.store.rating === 6) {
return true;
} else {
return false;
}
};
//The data that is shown
$scope.data = [
{
name: "product1",
price: 198,
rating: 1
},
{
name: "product2",
price: 200,
rating: 5
},
{
name: "product3",
price: 200,
rating: 2
},
{
name: "product4",
price: 10,
rating: 3
},
{
name: "product5",
price: 200,
rating: 3
},
{
name: "product6",
price: 400,
rating: 5
}
Pluker:
http://plnkr.co/edit/RhJic3KYE0Lc42FJ2lOx?p=preview
You can move the logic to a function and call the function on button ng-click,
$scope.filter = function(){
$scope.filtereddata = [];
angular.forEach($scope.data,function(key,value){
if(key.rating === $scope.filterItem.store.rating)
$scope.filtereddata.push(key);
})
}
HTML
<button ng-click="filter()">search</button>
and ng-repeat should be based on the filtered data,
<li data-ng-repeat="item in filtereddata | orderBy:'price':reverse ">
Name: {{item.name}} Price: {{item.price}} Rating: {{item.rating}}
</li>
DEMO
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
//Contains the filter options
$scope.filterOptions = {
stores: [
{id : 2, name : 'Show All', rating: 6 },
{id : 3, name : 'Rating 5', rating: 5 },
{id : 4, name : 'Rating 4', rating: 4 },
{id : 5, name : 'Rating 3', rating: 3 },
{id : 6, name : 'Rating 2', rating: 2 },
{id : 7, name : 'Rating 1', rating: 1 }
]
};
//Contains the sorting options
$scope.sortOptions = {
stores: [
{id : 1, name : 'Price Highest to Lowest' },
{id : 2, name : 'Price Lowest to Highest' },
]
};
//Mapped to the model to filter
$scope.filterItem = {
store: $scope.filterOptions.stores[0]
}
//Mapped to the model to sort
$scope.sortItem = {
store: $scope.sortOptions.stores[0]
};
//Watch the sorting model - when it changes, change the
//ordering of the sort (descending / ascending)
$scope.$watch('sortItem', function () {
console.log($scope.sortItem);
if ($scope.sortItem.store.id === 1) {
$scope.reverse = true;
} else {
$scope.reverse = false;
}
}, true);
$scope.filter = function(){
$scope.filtereddata = [];
angular.forEach($scope.data,function(key,value){
if(key.rating === $scope.filterItem.store.rating)
$scope.filtereddata.push(key);
})
}
//The data that is shown
$scope.data = [
{
name: "product1",
price: 198,
rating: 1
},
{
name: "product2",
price: 200,
rating: 5
},
{
name: "product3",
price: 200,
rating: 2
},
{
name: "product4",
price: 10,
rating: 3
},
{
name: "product5",
price: 200,
rating: 3
},
{
name: "product6",
price: 400,
rating: 5
}
];
});
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
Filter:
<select ng-model="filterItem.store" ng-options="item.name for item in filterOptions.stores">
</select>
<button ng-click="filter()">search</button>
Sort:
<select ng-model="sortItem.store" ng-options="item.name for item in sortOptions.stores">
</select>
<p>
<strong>Selected Filter dropdown item: </strong> {{filterItem.store.name}}
</p>
<p>
<strong>Selected Sort dropdown item: </strong> {{sortItem.store.name}}
</p>
<ul>
<!-- We are getting the data first, filtering the data and then sorting the data based
on the select options -->
<li data-ng-repeat="item in filtereddata | orderBy:'price':reverse ">
Name: {{item.name}} Price: {{item.price}} Rating: {{item.rating}}
</li>
</ul>
<table>
<tr>
<th>Name</th>
<th>Price</th>
<th>Rating</th>
</tr>
<tr ng-repeat="item in data | filter:customFilter">
<td ng-click="">
{{item.name}}</td>
<td>{{item.price}}</td>
<td>{{item.rating}}</td>
</tr>
</table>
</body>
</html>

VueJS access model from method

In VueJS I am setting model data based on user actions. I want to access the model from a method to update an element.
In the code below, when the user changes the first select list, I want to update the second select list to show the id property of the first list. As it is the upper list works OK but the lower list id property is not updated on upper list change:
<html lang="en">
<head>
<meta charset="utf-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.1/vue.js"></script>
</head>
<body>
<div id="editor">
<form id="query" methods="GET">
<div id="form_container" class="row">
<div class="form-group">
<label for="choice-selector">Choices</label>
<select class="form-control" id="choice-selector" v-model="choice_id" v-on:change="refreshOptions">
<option v-for="item in choices" v-bind:value="item.id">
{{ item.name }}
</option>
</select>
<span>Current choice id: {{ choice_id }}</span>
<br>
<label for="option-selector">Options</label>
<select class="form-control" id="option-selector" v-model="option_id" >
<option v-for="item in options" v-bind:value="item.id">
{{ item.name }}
</option>
</select>
<span>Current option id: {{ option_id }}</span>
</div>
</div>
</div>
<script>
let index = 0;
new Vue({
el: '#editor',
data: {
choice_id: '1',
choices: [
{ id: '1', name: 'Choice A' },
{ id: '2', name: 'Choice B' },
{ id: '3', name: 'Choice C' }
],
option_id: '1',
options: [
]
},
ready: function startFetch() {
this.refreshOptions();
},
methods: {
refreshOptions: function refreshOptionList() {
console.log(">>refreshOptionList() index:" + index);
const vm = this;
const newOptions = [{ id: index, name: 'Option based on choices list id: ' + vm.choice_id }];
vm.$set('options', newOptions);
index += 1;
}
},
});
</script>
</body>
</html>
Any ideas?
In Vue 2.x vm.$set is an alias for Vue.set and it takes 3 parameters: target, key and value so you should use it like this:
vm.$set(this, 'options', newOptions);
Or you can just assign newOptions to this.options
this.options = newOptions;
Working example: https://plnkr.co/edit/lFDm7wxb56h81EAwuUNc

Categories

Resources