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('');
}
}
Related
I'm using RactiveJS 1.4.1 and asp.net mvc core razor pages.
I put the html to a script-template in the page. And call it in RactiveJS to render. Rendering works fine, but button click action, the method "addToCard" is not working. I tried to move addToCard function under data section - not works. Also I tried to call addToCard function like this "ractive.on( 'addToCart' ..." It not worked too.
Here is the code;
#{
ViewData["Title"] = "Ractive Examples - ForEach";
}
<div class="card-body">
<script id="testTemplate" type="text/html">
<h1>Let's shop!</h1>
<ul>
{{#each items: i}}
<li>
<p>{{i+1}}: {{description}}</p>
<label><input value='{{qty}}'> Quantity</label>
<!-- when the user clicks this button, add {\{qty}} of this item -->
<button on-click="addToCart:{{this}},{{qty}}" type="button">Add to cart</button>
</li>
{{/each}}
</ul>
</script>
<div id="container"></div>
</div>
#section Scripts
{
<script>
// Predefined data, items to be added to cart.
const items_data = [
{ description: "Asset 1", qty: 1 },
{ description: "Asset 2", qty: 21 },
{ description: "Asset 3", qty: 35 },
{ description: "Asset 4", qty: 0 },
{ description: "Asset 5", qty: 5 }
];
const ractive = new Ractive({
el: '#container',
template: "#testTemplate",
data: {
items: items_data
},
on: {
addToCart: function ( event, item, qty ) {
// Do something with the item and qty
console.log( item );
console.log( qty );
}
}
});
</script>
}
I took this example directly from RactiveJS documentation; https://www.ractivejs.org/docs/latest/proxy-events.html - But this is not working too (button displays nothing)...
#{
ViewData["Title"] = "Ractive Examples - ForEach";
}
<div class="card-body">
<script id="testTemplate" type="text/html">
<h1>Let's shop!</h1>
<ul>
{{#each items: i}}
<li>
<p>{{i+1}}: {{description}}</p>
<label><input value='{{qty}}'> Quantity</label>
<!-- when the user clicks this button, add {\{qty}} of this item -->
<button on-click='addToCart:{{this}},{{qty}}'>Add to cart</button>
</li>
{{/each}}
</ul>
</script>
<div id="container"></div>
</div>
#section Scripts
{
<script>
// Predefined data, items to be added to cart.
const items_data = [
{ description: "Asset 1", qty: 1 },
{ description: "Asset 2", qty: 21 },
{ description: "Asset 3", qty: 35 },
{ description: "Asset 4", qty: 0 },
{ description: "Asset 5", qty: 5 }
];
const ractive = new Ractive({
el: '#container',
template: "#testTemplate",
data: {
items: items_data
}
});
ractive.on( 'addToCart', function ( event, item, qty ) {
console.log( 'Adding ' + qty + ' of ' + item.description + ' to cart');
});
</script>
}
What am I missing?
Update: I can use data- attribute, i know but I'm searching for better way to do it.*
const ractive = Ractive({
el: '#container',
template: `
<ul>
{{#each items: i}}
<li>
<p>{{i+1}}: {{description}}</p>
<label><input value='{{qty}}'> Quantity</label>
<button type="button" data-id="{{i}}" on-click="addToCart">Push me!</button>
</li>
{{/each}}
</ul>
`,
data: {
items: items_data
},
on: {
addToCart (event) {
const id = event.node.getAttribute('data-id');
console.log("Item of the id is: " + id);
}
}
});
How to loop in vue.js through a filtered list with an input parameter given?
Pls. take into consideration that filtering like <li v-for="x in todos_filtered" v-if="x.person == 'Mike'"> should be avoided with many records.
Here the code which returns an empty list instead of 2 elements.
<html>
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.14/dist/vue.js"></script>
<body>
<div id="app">
<ul>
<li v-for="x in todos_filtered('Mike')">
{{ x.text }}
</li>
</ul>
</div>
<script>
myObject = new Vue({
el: '#app',
data: {
todos: [
{ person: 'Mike', text: 'Learn JavaScript' },
{ person: 'Luke', text: 'Learn Vue.js' },
{ person: 'Mike', text: 'Build Something Awesome' }
]
},
computed: {
todos_filtered(person) {
return this.todos.filter((x) => x.person === person);
}
},
})
</script>
</body>
</html>
The computed property should return a function that take the person as parameter:
computed: {
todos_filtered() {
return (person)=> this.todos.filter((x) => x.person === person);
}
},
I'm trying to implement a load more button to my code. I would be able to this in javascript but I can't find a similar way in vue.
This is my vue code. I've tried asking the element with the company id but it's not reactive so I can't just change the style.
<main>
<ul>
<li v-for="company in companiesdb" :key="company.id" v-bind:id="company.id" ref="{{company.id}}" style="display: none">
{{company.name}}<br>
{{company.email}}
</li>
</ul>
</main>
this is my failed atempt of doing it in javascript but as I've mentioned before ref is not reactive so I can't do it this way
limitView: function (){
const step = 3;
do{
this.numberCompaniesVis ++;
let li = this.$refs[this.numberCompaniesVis];
li.style = "display: block";
}while (this.numberCompaniesVis % 3 != step)
}
I think the way you are approaching this problem is a little complex. Instead, you can create a computed variable that will change the number of lists shown.
Here's the code
<template>
<div id="app">
<ul>
<li v-for="(company, index) in companiesLoaded" :key="index">
{{ company }}
</li>
</ul>
<button #click="loadMore">Load</button>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
companiesdb: [3, 4, 1, 4, 1, 2, 4, 4, 1],
length: 5,
};
},
methods: {
loadMore() {
if (this.length > this.companiesdb.length) return;
this.length = this.length + 3;
},
},
computed: {
companiesLoaded() {
return this.companiesdb.slice(0, this.length);
},
},
};
</script>
So instead of loading the list from companiesdb, create a computed function which will return the new array based on companiesdb variable. Then there's the loadMore function which will be executed every time user clicks the button. This function will increase the initial length, so more lists will be shown.
Here's the live example
Just use computed property to create subset of main array...
const vm = new Vue({
el: '#app',
data() {
return {
companies: [
{ id: 1, name: "Company A" },
{ id: 2, name: "Company B" },
{ id: 3, name: "Company C" },
{ id: 4, name: "Company D" },
{ id: 5, name: "Company E" },
{ id: 6, name: "Company F" },
{ id: 7, name: "Company G" },
{ id: 8, name: "Company H" },
{ id: 9, name: "Company I" },
{ id: 10, name: "Company J" },
],
companiesVisible: 3,
step: 3,
}
},
computed: {
visibleCompanies() {
return this.companies.slice(0, this.companiesVisible)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<ul>
<li v-for="company in visibleCompanies" :key="company.id" :id="company.id">
{{company.name}}
</li>
</ul>
<button #click="companiesVisible += step" v-if="companiesVisible < companies.length">Load more...</button>
</div>
I have to build a quiz/survey app in vue.js, I'm pretty new to vue and still trying to learn it. I have a quiz/survey that asks different questions depending on what the user answers in the initial question.
so if the user picks yes it will display question 2 if the user picks no it will display question 3 etc.
I'm not sure what the best way of going around it but so far I have this.
Is there anyway i can use the value of my answer as the questionIndex after a person clicks next?
JS file:
"use strict";
let quiz = {
title: "Asbestos Quiz",
questions: [
{
text: 'Do you need help with an Asbestos Survey?',
answers: [
{
text: "Yes",
value: '2'`enter code here`
},
{
text: "No",
value: '3'
},
]
},
{
text: 'Was your property constructed pre 2000',
answers: [
{
text: "Yes",
value: '4'
},
{
text: "No",
value: '5'
},
]
},
{
text: 'Do you need an Asbestos Management plan?',
answers: [
{
text: "Yes",
value: '6'
},
{
text: "No",
value: '7'
},
]
}
]
};
var app = new Vue({
el: "#app",
data: {
quiz: quiz,
questionIndex: 0,
responses: [],
errors: [],
error: ''
},
methods: {
prev: function() {
this.questionIndex--;
},
next: function() {
if (this.responses[this.questionIndex] === undefined) {
this.errors[this.questionIndex] = 1;
this.error = 'Please select your answer';
}
else {
this.errors[this.questionIndex] = 0;
this.questionIndex++;
}
},
score: function() {
},
playAgain: function() {
this.questionIndex = 0;
}
}
});
HTML:
<html lang="en">
<head>
<title>Vue quiz/survey</title>
<meta name="viewport" content="width=device-width"/>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<!-- <link rel="stylesheet" href="index.css"> -->
</head>
<body>
<div id="app">
<div class="container">
<div class="jumbotron mt-3">
<h1 class="mb-5">{{ quiz.title }}</h1>
<hr>
<p v-if="errors[questionIndex]" class="alert alert-danger">
{{ error }}
</p>
<div v-for="(question, index) in quiz.questions">
<div v-show="index === questionIndex">
<h4 class="mt-5 mb-3">{{ question.text }}</h4>
<div v-for="answer in question.answers" class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio"
:value="answer.value"
:name="index"
v-model="responses[index]">
{{answer.text}}
</label>
</div>
<div class="mt-5">
<button
class="btn btn-primary"
v-if="questionIndex > 0"
#click="prev">
prev
</button>
<button class="btn btn-secondary" #click="next">
next
</button>
</div>
</div>
</div>
<div v-show="questionIndex === quiz.questions.length">
<h3>Your Results</h3>
<p>
You are: {{ score() }}
</p>
<button class="btn btn-success" #click="playAgain">
Play Again!
</button>
</div>
</div>
</div>
</div>
<script type="text/javascript" src="index.js"></script>
</body>
</html>
I thought that this sounded like a potentially interesting exercise, so I spent some time creating an implementation in a Vue CLI sandbox app that I built and use for trying out various ideas.
I learned a few things, and hopefully you will get something out of it. I left the 'Previous' functionality as a TODO if you decide you like it and want implement that yourself.
QuizQuestions.vue
<template>
<div class="quiz-questions">
<div class="jumbotron mt-3">
<h1 class="mb-5">{{ quiz.title }}</h1>
<hr>
<question v-if="!showResults" :question="currentQuestion" #answer-selected="processAnswer" />
<div class="mt-5">
<button class="btn btn-primary" v-if="currentQuestionId > 1 && !showResults" #click="getPreviousQuestion">
prev
</button>
<button v-if="!showResults" class="btn btn-secondary" #click="getNextQuestion">
{{ nextButtonLabel }}
</button>
</div>
<div v-if="showResults">
<h3>Your Results</h3>
<table class="table table-bordered">
<thead>
<tr>
<th>QUESTION</th>
<th>ANSWER</th>
</tr>
</thead>
<tbody>
<tr v-for="(response, index) in responses" :key="index">
<td>{{ getQuestionText(response.questionId) }}</td>
<td>{{ getAnswerText(response.answerId) }}</td>
</tr>
</tbody>
</table>
<button class="btn btn-success" #click="playAgain">
Play Again!
</button>
</div>
</div>
</div>
</template>
<script>
import quiz from './quiz.js';
import Question from '#/components/stackoverflow/Question'
export default {
components: {
Question
},
data() {
return {
quiz: quiz,
currentQuestionId: 1,
currentAnswerId: 1,
previousQuestionId: 0,
responses: [],
showResults: false,
errors: [],
error: ''
}
},
computed: {
currentQuestion() {
return this.quiz.questions.find( question => {
return question.id === this.currentQuestionId;
})
},
nextQuestionId() {
let retVal = 0;
if (this.currentAnswerId > 0) {
let tempAnswer = this.currentQuestion.answers.find( answer => {
return answer.id === this.currentAnswerId;
});
retVal = tempAnswer.nextQuestionId;
}
return retVal;
},
lastQuestion() {
return this.currentQuestion.answers[0].nextQuestionId === 0;
},
nextButtonLabel() {
return this.lastQuestion ? 'Finish' : 'Next';
}
},
methods: {
getPreviousQuestion() {
this.currentQuestionId = this.previousQuestionId;
},
getNextQuestion() {
// TODO: Look for existing response for this question in case the 'Previous' button was pressed
// If found, update answer
// Store current question id and answer id in responses
let response = { questionId: this.currentQuestionId, answerId: this.currentAnswerId };
this.responses.push(response);
if (this.lastQuestion) {
this.showResults = true;
return;
}
this.previousQuestionId = this.currentQuestionId;
this.currentQuestionId = this.nextQuestionId;
//console.log(this.responses);
},
getQuestionText(id) {
let result = this.quiz.questions.find( question => {
return question.id === id;
});
return result.text;
},
getAnswerText(id) {
// NOTE: Since answers are currently limited to '1 = Yes' and '2 = No',
// this method does not need to involve any look up
return id === 1 ? 'Yes' : 'No';
},
processAnswer(selectedAnswerId) {
this.currentAnswerId = selectedAnswerId;
},
score() {
return 'TODO'
},
playAgain() {
this.currentQuestionId = 1;
this.showResults = false;
this.responses = [];
}
}
}
</script>
Question.vue
<template>
<div class="question">
<h4 class="mt-5 mb-3">{{ question.text }}</h4>
<div class="form-check" v-for="(answer, idx) in question.answers" :key="idx">
<input class="form-check-input" type="radio"
:value="answer.id" v-model="answerId" #change="answerSelected">
<label class="form-check-label">
{{answer.text}}
</label>
</div>
</div>
</template>
<script>
export default {
props: {
question: {
type: Object,
required: true
}
},
data() {
return {
answerId: 1
}
},
watch:{
question() {
// Reset on new question
this.answerId = 1;
}
},
methods: {
answerSelected() {
this.$emit('answer-selected', this.answerId);
}
}
}
</script>
I also modified your test data by adding various ID properties to help with tracking, as well as created a few more placeholder questions.
quiz.js
const quiz = {
title: "Asbestos Quiz",
questions: [
{
id: 1,
text: 'Do you need help with an Asbestos Survey?',
answers: [
{
id: 1,
text: "Yes",
nextQuestionId: 2
},
{
id: 2,
text: "No",
nextQuestionId: 3
},
]
},
{
id: 2,
text: 'Was your property constructed pre 2000',
answers: [
{
id: 1,
text: "Yes",
nextQuestionId: 4
},
{
id: 2,
text: "No",
nextQuestionId: 5
},
]
},
{
id: 3,
text: 'Do you need an Asbestos Management plan?',
answers: [
{
id: 1,
text: "Yes",
nextQuestionId: 6
},
{
id: 2,
text: "No",
nextQuestionId: 7
},
]
},
{
id: 4,
text: 'Question 4',
answers: [
{
id: 1,
text: "Yes",
nextQuestionId: 0
},
{
id: 2,
text: "No",
nextQuestionId: 0
},
]
},
{
id: 5,
text: 'Question 5',
answers: [
{
id: 1,
text: "Yes",
nextQuestionId: 0
},
{
id: 2,
text: "No",
nextQuestionId: 0
},
]
},
{
id: 6,
text: 'Question 6',
answers: [
{
id: 1,
text: "Yes",
nextQuestionId: 0
},
{
id: 2,
text: "No",
nextQuestionId: 0
},
]
},
{
id: 7,
text: 'Question 7',
answers: [
{
id: 1,
text: "Yes",
nextQuestionId: 0
},
{
id: 2,
text: "No",
nextQuestionId: 0
},
]
}
]
};
export default quiz;
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>