How can I toggle designs using inputs?
<section id="assignment">
<!-- 1) Fetch the user input and use it as a CSS class -->
<!-- The entered class should be added to the below paragraph -->
<input type="text" v-on:input="setUser" />
<!-- (available classes: "user1", "user2") -->
<p :class="{user}">
Style me!
</p>
<button>Toggle Paragraph</button>
<!-- 2) Use the "visible" and "hidden" classes to show/ hide the above paragraph -->
<!-- Clicking the button should toggle between the two options -->
<!-- 3) Add dynamic inline styling to the below paragraph and let the user enter a background-color -->
<input type="text" />
<p>Style me inline!</p>
</section>
I have already made user1 and user2 classes in css file, but when i try to output user as a class it is showing just user doesnot matter what i write in input field.
Please take a look at following demo:
const app = Vue.createApp({
data() {
return {
user: null,
bgcolor: null,
toggle: false,
users: []
};
},
computed: {
classes() {
return this.users.map(u => 'user' + u.id)
}
},
mounted() {
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json())
.then(json => this.users = json)
}
})
app.mount('#demo')
.user1 {
color: red;
}
.user2 {
color: green;
}
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<section id="assignment">
<select v-model="user">
<option v-for="(cls, i) in classes" :key="i">
{{ cls }}
</option>
</select>
<input type="text" v-model="user" />
<p :class="user">
Style me!
</p>
<button #click="toggle = !toggle">Toggle Paragraph</button>
<input v-model="bgcolor" type="color" />
<p v-if="toggle" :style="`background-color: ${bgcolor};`">Style me inline!</p>
</section>
</div>
Related
I have a problem. It's my first Vue.js project and I need help to solve the following problem. First I get a response from my API, then I get a list of projects and I want to find the project with the same ID as the url parameter. When I try to open the view my console logs the following error:
TypeError: Cannot read property 'title' of undefined
However, it then renders the right project into the template.
Code:
<template>
<div id="wrapper">
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css"
integrity="sha512-+4zCK9k+qNFUR5X+cKL9EIR+ZOhtIloNl9GIKS57V1MyNsYpYcUrUeQc9vNfzsWfV28IaLL3i96P9sdNyeRssA=="
crossorigin="anonymous"
/>
<Sidebar></Sidebar>
<div id="content">
<Navbar></Navbar>
<div id="headline">
<ul>
<li>
<h1>Projekt Details</h1>
<Popup></Popup>
</li>
</ul>
</div>
<div id="grid" class="module-grid module-grid-2">
<div class="card">
<div class="card-head">
<div>
<h3>Meta Daten</h3>
</div>
<div></div>
</div>
<div class="card-body">
<ul v-if="filtered_projects != null">
<li>
<div class="list-info">
<p>Projektnummer: {{ filtered_projects.id }}</p>
</div>
</li>
<li>
<div class="list-info">
<p>Autor: {{ filtered_projects.author }}</p>
</div>
</li>
<li>
<div class="list-info">
<p>Firma: {{ filtered_projects.company }}</p>
</div>
</li>
<li>
<div class="list-info">
<p>
Erstellt am:
{{
new Date(filtered_projects.created_at)
.toLocaleString()
.split(",")[0]
}}
</p>
</div>
</li>
<li>
<div class="list-info">
<p>
Letzte Änderung am:
{{
new Date(filtered_projects.updated_at)
.toLocaleString()
.split(",")[0]
}}
</p>
</div>
</li>
</ul>
</div>
</div>
<div class="card">
<div class="card-head">
<div>
<h3>Projekt Übersicht</h3>
</div>
<div></div>
</div>
<div class="card-body">
<form class="edit-form" #submit.prevent="submitProject()">
<label for="title">Überschrift*</label>
<input
v-model="filtered_projects.title"
name="title"
id="title"
class="input"
type="text"
required
maxlength="16"
/>
<label for="text">Text*</label>
<textarea
v-model="filtered_projects.text"
name="text"
id="text"
class="input"
type="text"
required
rows="6"
/>
<label for="finish">Abgeschlossen</label>
<input
v-model="filtered_projects.finish"
name="finish"
id="finish"
class="input w-auto"
type="checkbox"
/>
<button v-if="state.user_info.id === filtered_projects.author || state.user_info.admin === true" type="submit" class="second-btn btn">
Aktualisieren
</button>
</form>
<button v-on:click="deleteProject()" v-if="state.user_info.id === filtered_projects.author || state.user_info.admin === true" class="btn delete-btn">Löschen</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { computed, reactive } from "vue";
import { useStore } from "vuex";
import { useRoute, useRouter } from "vue-router";
import Sidebar from "../components/Sidebar.vue";
import Popup from "../components/Popup.vue";
import Navbar from "../components/Navbar.vue";
export default {
name: "ProjectDetails",
components: {
Sidebar,
Popup,
Navbar,
},
setup() {
const store = useStore();
const route = useRoute();
const router = useRouter();
store.dispatch("company_projects/getProjectList");
store.dispatch("user_info/getUserInfo");
store.dispatch("companies/getCompaniesList");
const state = reactive({
query: route.params.id,
company_projects: computed(
() => store.getters["company_projects/getProjectlist"]
),
user_info: computed(
() => store.getters["user_info/getUserInfo"]
),
companies: computed(
() => store.getters["companies/getCompanieslist"]
),
user_auth_data: computed(
() => store.getters["auth/getAuthData"]
),
});
const filtered_projects = computed(() => state.company_projects.find(obj => {
return obj.id == parseInt(state.query)
}))
async function submitProject() {
await store
.dispatch("company_projects/submitProject", {
id: state.company_project.id,
company: state.company_project.company,
author: state.company_project.author,
title: state.company_project.title,
text: state.company_project.text,
finish: state.company_project.finish,
})
.catch((err) => {
console.log(err);
});
}
async function deleteProject() {
await store
.dispatch("company_projects/deleteProject", {
id: state.company_project.id,
})
.catch((err) => {
console.log(err);
});
router.push("/project");
}
return {
state,
submitProject,
deleteProject,
filtered_projects,
route,
};
},
};
</script>
Do you know a way to solve it better than me?
Thank you very much.
Since you are dispatching the company_projects/getProjectList action (which I suppose is responsible for fetching data from the server) directly in the setup, it is very likely that at the moment your component will render for the 1st time, the company_projects/getProjectList getter (and consequently the state.company_projects computed) will return an empty array and thus filtered_projects is undefined (find)
Which means the part of your template which needs filtered_projects to have a value should be protected with v-if
<form class="edit-form" #submit.prevent="submitProject()" v-if="filtered_projects">
In the following Vue Component I want to loop through dwarfs array. And as long as I am in the current component, everything is fine (TEST) and also all the following properties are correct.
Currenct_Component.vue :
<template>
<div>
<h2>Stamm: {{ tribeName }}</h2>
<div class="card-container">
<div class="card" style="width: 18rem;" v-for="dwarf in dwarfs" :key="dwarf.name">
<!-- TEST -->
<p>{{dwarf}}</p>
<!-- CHILD COMPONENT -->
<app-modal
:showModal="showModal"
:targetDwarf="dwarf"
#close="showModal = false"
#weaponAdded="notifyApp"
/>
<!-- <img class="card-img-top" src="" alt="Card image cap">-->
<div class="card-body">
<h3 class="card-title" ref="dwarfName">{{ dwarf.name }}</h3>
<hr>
<ul class="dwarf-details">
<li><strong>Alter:</strong> {{ dwarf.age }}</li>
<li><strong>Waffen:</strong>
<ul v-for="weapon in dwarf.weapons">
<li><span>Name: {{ weapon.name }} | Magischer Wert: {{ weapon.magicValue }}</span></li>
</ul>
</li>
<li><strong>Powerfactor:</strong> {{ dwarf.weapons.map(weapon => weapon.magicValue).reduce((accumulator, currentValue) => accumulator + currentValue) }}</li>
</ul>
<button class="card-button" #click="showModal = true"><span class="plus-sign">+</span> Waffe</button>
</div>
</div>
</div>
<button id="backBtn" #click="onClick">Zurück</button>
</div>
</template>
<script>
import Modal from './NewWeaponModal.vue';
export default {
data() {
return {
showModal: false,
}
},
components: { appModal : Modal },
props: ['tribeName', 'dwarfs'],
methods: {
onClick() {
this.$emit('backBtn')
},
notifyApp() {
this.showModal = false;
this.$emit('weaponAdded');
}
},
}
</script>
But when I bind the element dwarf to the Child Component <app-modal/> it changes to the next dwarf in the array dwarfs (TEST) - (So as the result when i add a new weapon in the modal-form it gets added to the second dwarf...):
Child_Component.vue :
<template>
<div>
<div class="myModal" v-show="showModal">
<div class="modal-content">
<span #click="$emit('close')" class="close">×</span>
<h3>Neue Waffe</h3>
<!-- TEST -->
<p>{{ targetDwarf }}</p>
<form>
<input
type="text"
placeholder="Name..."
v-model="weaponName"
required
/>
<input
type="number"
placeholder="Magischer Wert..."
v-model="magicValue"
required
/>
<button #click.prevent="onClick">bestätigen</button>
</form>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
weaponName: '',
magicValue: '',
}
},
props: ['showModal', 'targetDwarf'],
methods: {
onClick() {
if(this.weaponName !== '' &&
Number.isInteger(+this.magicValue)) {
let newData = {...this.dwarf};
newData['weapons'] = [
...this.dwarf['weapons'],
{
"name": this.weaponName,
"magicValue": Number.parseInt(this.magicValue)
},
];
this.$http.post("https://localhost:5019/api", newData)
.then(data => data.text())
.then(text => console.log(text))
.catch(err => console.log(err));
this.$emit('weaponAdded');
} else {
alert('You should fill all fields validly')
}
},
}
}
</script>
It looks like you have the <app-modal/> component inside of the v-for="dwarf in dwarfs" loop, but then the control for showing all of the modal components created by that loop is just in one variable: showModal. So when showModal is true, the modal will show each of the dwarfs, and I'm guessing the second dwarf's modal is just covering up the first one's.
To fix this, you could move the <app-modal /> outside of that v-for loop, so there's only one instance on the page, then as part of the logic that shows the modal, populate the props of the modal with the correct dwarf's info.
Something like this:
<div class="card-container">
<div class="card" v-for="dwarf in dwarfs" :key="dwarf.name">
<p>{{dwarf}}</p>
<div class="card-body">
<button
class="card-button"
#click="() => setModalDwarf(dwarf)"
>
Waffe
</button>
</div>
</div>
<!-- Move outside of v-for loop -->
<app-modal
:showModal="!!modalDwarfId"
:targetDwarf="modalDwarfId"
#close="modalDwarfId = null"
#weaponAdded="onDwarfWeaponAdd"
/>
</div>
export default {
//....
data: () => ({
modalDwarfId: null,
)},
methods: {
setModalDwarf(dwarf) {
this.modalDwarfId = drawf.id;
},
onDwarfWeaponAdd() {
//...
}
},
}
You could then grab the correct dwarf data within the modal, from the ID passed as a prop, or pass in more granular data to the modal so it's more "dumb", which is the better practice so that the component isn't dependent on a specific data structure. Hope that helps
Courtesy of #Joe Dalton's answer, a bit alternated for my case:
<div class="card" style="width: 18rem;" v-for="dwarf in dwarfs" :key="dwarf.name">
...
<button class="card-button" #click="setModalDwarf(dwarf)"><span class="plus-sign">+</span> Waffe</button>
<div>
<app-modal
:showModal="showModal"
:targetDwarf="currentDwarf"
#close="showModal = false"
#weaponAdded="notifyApp"
/>
<script>
import Modal from './NewWeaponModal.vue';
export default {
data() {
return {
showModal: false,
currentDwarf: null,
}
},
components: { appModal : Modal },
props: ['tribeName', 'dwarfs'],
methods: {
setModalDwarf(dwarf) {
this.currentDwarf = dwarf;
this.showModal = true;
},
...
}
</script>
I've been making a simple To Do app with VueJS. I have the add new todo, delete todo and mark as done functionalities done, but I'm struggling with the "Double Click to edit a task" feature.
I've added an input field which should appear when the user double clicks on the task to edit it but nothing seems to happen? Any help would be awesome :)
App.vue:
<template>
<div id="app">
<div class="container">
<div class="row">
<h1>VueJS To Do Manager:</h1>
</div>
</div>
<div class="container">
<div class="row">
<input class="new-todo input-group col-xs-12"
placeholder="Enter a task and press enter. Use the checkbox to mark them as done."
v-model="newTodo"
#keyup.enter="addTodo">
</div>
</div>
<TodoCard v-for="(todo, key) in todos"
:todo="todo"
:key="key"
#remove="removeTodo(key)"/>
</div>
</template>
<script>
import TodoCard from './components/TodoCard'
export default {
data () {
return {
todos: [],
newTodo: ''
}
},
components: {
TodoCard
},
methods: {
addTodo: function () {
// Store the input value in a variable
let inputValue = this.newTodo && this.newTodo.trim()
// Check to see if inputed value was entered
if (!inputValue) {
return
}
// Add the new task to the todos array
this.todos.push(
{
text: inputValue,
done: false
}
)
// Set input field to empty
this.newTodo = ''
},
removeTodo: function (key) {
this.todos.splice(key, 1)
}
}
}
</script>
TodoCard.vue component:
<template>
<div id="todo">
<div class="container">
<div class="row">
<input class="check" type="checkbox" />
<h3 class="col strikethrough"
#dblclick="editTodo(todo)">{{ todo.text }}</h3>
<div v-show="todo.edit == false">
<input v-show="todo.edit == true"
v-model="todo.title"
v-on:blur="todo.edit=false; $emit('update')"
#keyup.enter="todo.edit=false; $emit('update')">
</div>
<hr>
<button #click="removeTodo"
type="button"
class="btn btn-danger btn-sm">Delete</button>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['todo'],
methods: {
removeTodo: function (todo) {
this.$emit('remove')
},
editTodo: function (todo) {
this.editedTodo = todo
}
}
}
</script>
I think you don't set todo.edit to true when double click todo description. Moreover the div that contains the todo edit input has v-show="todo.edit == false" while it should be v-show="todo.edit == true" or just v-show="todo.edit" if you are sure that todo.edit is always a boolean.
I am trying to create a chat style form. So a user inputs their data and then uses the button within my template with the class of continue-btn.
As you can see when the continue-btn is pressed it uses the nextStep method which adds 1 to the counter data property.
Within my template I then use v-if="counter >= 1" to display the next section of the chat dialog and input field.
I am then trying to use scrollTop to automatically scroll the page to the new section with the id of #conversation__tram-1. I originally tried running this block of code just after the counter had been given a value of 1:
const container = this.$el.querySelector("#conversation__tram-" + this.counter);
container.scrollTop = container.scrollHeight;
This didn't work though because I'm guessing the #conversation__tram-1 element hadn't been added to the DOM yet.
So for the sake of testing I tried wrapping it in a timeout function:
setTimeout(function(){
const container = this.$el.querySelector("#conversation__tram-" + this.counter);
container.scrollTop = container.scrollHeight;
}, 3000);
However I am left with this error when trying this:
Uncaught TypeError: Cannot read property 'querySelector' of undefined
Here is my whole single vue file:
<template>
<div id="conversation-app">
<!-- <div v-for="item in items">
{{ item.text }}
</div> -->
<div class="conversation__track">
<div id="conversation__tram-0">
<div class="conversation__item agent">
<img src="/assets/cdn.annuityadvicecentre.dev/images/theme-f/michael-chat-agent.jpg" class="conversation__item-prof-img" alt="Michael Chat Agent" />
<div class="conversation__item-content">
<p>
Hello my name is {{ agent }}, we'll compare the whole annuity market to bring you back the best annuity rates from the top providers for you. Let's get started, what's your name?
</p>
</div>
</div>
<div class="conversation__item customer" id="title-fullname">
<div class="conversation__item-content">
<p>
Hi {{ agent }}, my name is...
</p>
<div class="row">
<div class="col-4">
<select id="title" class="field-title" name="payload[title]"><option value="mr">Mr</option><option value="mrs">Mrs</option><option value="miss">Miss</option><option value="ms">Ms</option></select>
</div>
<div class="col-8">
<input v-model="customerName" id="full_name" class="field-full_name" name="payload[full_name]" type="text">
</div>
</div>
</div>
</div>
</div>
<transition name="fade">
<div id="conversation__tram-1" v-if="counter >= 1">
<div class="conversation__item agent">
<img src="/assets/cdn.annuityadvicecentre.dev/images/theme-f/michael-chat-agent.jpg" class="conversation__item-prof-img" alt="Michael Chat Agent" />
<div class="conversation__item-content">
<p>
Thanks {{ firstName }}, nice to meet you. To process your instant quote please can I have your Pension Value?
</p>
</div>
</div>
<div class="conversation__item customer">
<div class="conversation__item-content">
<p>
Sure, my pension value is...
</p>
<input id="pension_value" class="field-pension_value" placeholder="£" pattern="\d*" name="payload[pension_value]" type="number">
<div class="error-wrap error_pension_value is-hidden" data-format="<div class="error-text">:message</div>"></div>
</div>
</div>
</div>
</transition>
<div id="conversation__buttons">
<button type="button" class="continue-btn"
v-on:click="nextStep"
>Continue <i class="fa fa-chevron-right" aria-hidden="true"></i></button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'conversation-app',
data () {
return {
agent: 'Brick',
counter: 0,
customerName: '',
}
},
methods: {
nextStep: function() {
this.counter += 1;
setTimeout(function(){
const container = this.$el.querySelector("#conversation__tram-" + this.counter);
container.scrollTop = container.scrollHeight;
}, 3000);
},
},
computed: {
firstName() {
return this.customerName.split(' ')[0];
}
}
}
</script>
Any idea why this isn't working? Thanks.
This is a good time to use arrow functions, as they preserve the context of this.
nextStep: function() {
this.counter += 1;
setTimeout(() => {
const container = this.$el.querySelector("#conversation__tram-" + this.counter);
container.scrollTop = container.scrollHeight;
}, 3000);
Altenatively, instead of the timeout you can use Vue.nextTick which is a more technically-correct way of doing this.
nextStep: function () {
this.counter += 1
this.$nextTick(() => { ... })
I'm trying to find out if one or both of the 2 colour checkboxes is selected, and depending on which one/pair is selected, objects of that colour will be outputted on the form. So if a user checked the "purple" check box then there will be a bunch of purple objects outputted to the form. If "purple" and "yellow" are both checked both purple and yellow objects will be outputted onto the screen. I've been trying to check if the checkbox is "true" to see if it selected but there is something wrong with my logic. http://codepen.io/MarkBond/pen/pJmrxV?editors=101 Thanks in advance
HTML
<html ng-app="formApp">
<head>
<!-- CSS -->
<!-- load up bootstrap and add some spacing -->
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
<style>
body { padding-top:50px; }
form { margin-bottom:50px; }
</style>
<!-- JS -->
<!-- load up angular and our custom script -->
<script src="http://code.angularjs.org/1.3.14/angular.js"></script>
<script src="app.js"></script>
</head>
<!-- apply our angular app and controller -->
<body ng-controller="FormController as formCtrl">
<div class="col-xs-12 col-sm-10 col-sm-offset-1">
<h2>Angular Checkboxes</h2>
<form>
<div class="checkbox">
<label>
<input type="checkbox" name="displayOption" ng-model="formData.displayOption.purple" ng-click="yourFunction()" />purple
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox" name="displayOption" ng-model="formData.displayOption.yellow" ng-click="yourFunction()" />yellow
</label>
</div>
</form>
<h2>Array/Message Output Area</h2>
<pre>
<div ng-repeat="object in formCtrl.objects">
{{ object.name }}
</div>
{{ message }}
</pre>
<!-- SHOW OFF OUR FORMDATA OBJECT -->
<h2>Boolean Test Area</h2>
<pre>
{{ formData }}
</pre>
</div>
</body>
</html>
AngularJS
angular.module('formApp', [])
.controller('FormController', ['$scope' ,function($scope) {
$scope.formData = {};
$scope.yourFuction = function(){
var purple = $scope.purple;
var yellow = $scope.yellow;
if (purple === true && yellow === true) {
this.objects = groupOne
} else if (purple === true) {
this.objects = groupTwo
} else if (yellow === true) {
this.objects = groupOne + groupTwo
}else{
this.message = 'Nothing Selected'
}
};
var groupOne = [
{ name: 'Grape'},
{ name: 'Wine'},
{ name: 'Toy Octipus'}
]
var groupTwo = [
{ name: 'Banana'},
{ name: 'Lemon'},
{ name: 'Yellow Highlighter'}
]
}]);
$scope.yourFuction should be $scope.yourFunction and for your if statements you should be checking for $scope.formData.displayOption.purple instead of $scope.purple
var purple = $scope.formData.displayOption.purple;
var yellow = $scope.formData.displayOption.yellow;
http://codepen.io/anon/pen/LVozZG?editors=101
https://docs.angularjs.org/api/ng/input/input%5Bcheckbox%5D