Add one component multiply times on one page - javascript

How can I place same component on one page multiply times? I have input component who get props and do some stuff. I need to place more than one inputs in one page and i thought i can just copy/paste my component but i get error because vue is thinking that all of my components are the same dom element. how can I put them?
index.vue
<template>
<div class="container">
<Input name="name" />
<Input name="surname" />
<Input name="pass" />
</div>
</template>
Input.vue
<template>
<div class="inputs">
<label class="inputs__label" :for="name">Имя</label>
<input
v-click-outside="moveR"
class="inputs__input"
:name="name"
type="text"
#click="moveL($event.target)"
/>
</div>
</template>
<script>
import vClickOutside from 'v-click-outside'
export default {
directives: {
clickOutside: vClickOutside.directive,
},
props: ['name'],
methods: {
moveR(e) {
console.log(e)
e.classList.add('inputs__lable_r')
},
moveL(e) {
console.log(e)
e.classList.remove('inputs__lable_r')
},
},
}
</script>
iam sorry i dont have a big baggage of knowledge of vue and google doesnt gave me needed information
i write on nuxt but i think its same trouble with vue

This is what it should be
moveR(e) {
e.target.classList.add('inputs__lable_r')
},
You were missing a e.target, hence it was not targeting the HTML element but rather the event.

Related

Bootstrap vue b-modal does not highlight the field on submission of the data

I am using the bootstrap vue to design my application. Within the application, I am using the b-modal. Some of the fields in b-modal are required so I would like to highlight them if the user has not provided information to them. In normal bootstrap which I have used in other applications, it was highlighting the field and showing a default message field is required but in the bootstrap-vue I am not getting any such message by default. Can someone please tell me what needs to be done about it?
Following is the bootstrap vue modal code I have:
<template>
<b-modal
id="formSubmission"
size="lg"
title="Basic Details"
:visible="visibility"
style="width: 100%"
#cancel="hideModal"
#ok="submitModal($event)"
>
<b-form-select v-model="type" class="form-control" required>
<b-form-select-option value="type1">
Type1
</b-form-select-option>
<b-form-select-option value="type2">
Type2
</b-form-select-option>
</b-form-select>
<div v-if="type == 'type1'">
<input
type="text"
class="form-control"
style="width:200px"
autocomplete="off"
placeholder="Enter Name"
:required="type == 'type1'"
>
</div>
</b-modal>
</template>
<script>
export default {
data () {
return {
visibility: true
}
},
methods: {
hideModal () {
this.visibility = false
},
submitModal (event) {
event.preventDefault()
}
}
}
</script>
<style>
</style>
What I want to do is highlight the field which is required during the submission? I want to know if there is an out-of-the-box way to do it rather than writing the function for each and every field.
Something like this:
The modal doesn't know you have input elements inside it, and that you want to validate it. Which is why nothing happens.
You can solve this in a few ways. The way i would recommend is to first create a form around your input fields with <b-form>.
Then when clicking on the OK button, we need to submit the form, as that will then validate the inputs and show an error if the requirements are filled.
We will then use the modal-footer slot, to overwrite the default footer and replace it with our own buttons. For the cancel button we'll use the cancel method from the slot scope, so that it will function as default.
However, for the OK button, we will use the form attribute and type="submit", to create a submit button for the form inside the modal. The form attribute takes the id from our form.
If the form is submitted succesfully, we'll need to hide the modal manually. In the snippet we use this.$bvModal.hide for this.
new Vue({
el: '#app',
data() {
return {
value: ''
}
},
methods: {
onSubmit() {
const {
value
} = this;
alert(`Submitted: ${value}`);
this.$bvModal.hide('my-modal')
}
}
})
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap#4.5.3/dist/css/bootstrap.min.css" />
<link href="https://unpkg.com/bootstrap-vue#2.21.2/dist/bootstrap-vue.css" rel="stylesheet" />
<script src="//unpkg.com/vue#2.6.12/dist/vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#2.21.2/dist/bootstrap-vue.min.js"></script>
<div id="app" class="p-4">
<b-btn v-b-modal.my-modal>Show Modal</b-btn>
<b-modal id="my-modal" title="Form Modal" visible>
<b-form id="my-form" #submit.prevent="onSubmit">
<b-input v-model="value" required minlength="3" autofocus placeholder="Write something.."></b-input>
</b-form>
<template #modal-footer="{ cancel }">
<b-btn #click="cancel">Cancel</b-btn>
<b-btn variant="primary" type="submit" form="my-form">OK</b-btn>
</template>
</b-modal>
</div>

How do you create a reusable Vue component with d3?

I'm trying to create a Chart.vue component based on D3. I need to be able to add multiple instances to a single page and keep each one separate.
I've tried to assign an ID generated with uuid to the div wrapping my component in the template:
<template>
<div :id=this.id>
<svg></svg>
</div>
</template>
The ID is created when the component is created.
<script>
import * as d3 from "d3";
import { v4 as uuidv4 } from "uuid";
export default {
...
created () {
this.id = uuidv4()
},
...
The chart is re-rendered when there is an update to the data passed in as props from the parent App.vue. To select the "correct" <svg> element that is owned by a particular instance of Chart I use the unique this.id in my renderChart method:
methods: {
renderChart(chart_data) {
const svg_width = 1000;
const svg_height = 600;
const svg = d3
.select("#" + this.id)
.select("svg")
.attr("width", svg_width)
.attr("height", svg_height);
...
Proceeding to add all the axes, data, etc.
If I add two such components to my App.vue template:
<template>
<div id="app">
<form action="#" #submit.prevent="getIssues">
<div class="form-group">
<input
type="text"
placeholder="owner/repo Name"
v-model="repository"
class="col-md-2 col-md-offset-5"
>
</div>
</form>
<Chart :issues="issues" />
<Chart :issues="issues" />
</div>
</template>
I see that they are added to the DOM with some uuid that's been created. When the data is updated and the renderChart function executes, both components get a copy of the "issues" data, but I only see one chart being created.
I'm quite novice with JavaScript, Vue and D3, so perhaps going about this the wrong way, but it seems like this should work?
Any help is appreciated.
Well, I seem to have found a solution, although I don't fully understand it, and I'm not sure why the initial approach didn't work (note: original approach did seem to work sometimes, but the behaviour was unpredictable).
To solve, I pass a unique ID to the component from the parent template as a prop and add it as the Chart component <div> id.
In App.vue:
<template>
<div id="app">
<form action="#" #submit.prevent="getIssues">
<div class="form-group">
<input
type="text"
placeholder="owner/repo Name"
v-model="repository"
class="col-md-2 col-md-offset-5"
>
</div>
</form>
<Chart id="chart1" :issues="issues" />
<Chart id="chart2" :issues="issues" />
</div>
</template>
Now I need to add id in the props of the Chart.vue and set a variable in the data() section.
<template>
<div :id=chart_id>
<svg></svg>
</div>
</template>
<script>
import * as d3 from "d3";
export default {
name: 'Chart',
props: ["issues", "id"],
data() {
return {
chart: null,
chart_id: this.id
};
},
...
I'm not sure why the uuid approach didn't work, but this seems more robust.

Access objects property in component

I'm trying to access email property of user object
{"name":"test", "email":"test#test.x"}
like this:
<input type="text" :placeholder="user.email">
When I try to access property in console -> console.log(user.email) everything work's just fine
component code ->
<template>
<div>
<form method="POST" action="smth.php">
<input type="text" :placeholder="user.email">
</form>
</div>
</template>
<script>
export default {
props: {
user: {
type: Object,
required: true
}
},
}
</script>
Component call ->
<div class="container">
<page-profile :user="{{ $user->toJson() }}"></page-profile>
</div>
Could you please show me the right way how to access the email property of user object?
Both console and npm watch show no errors.
The issue is with this code:
<div class="container">
<page-profile :user="{{ $user->toJson() }}"></page-profile>
</div>
You don't need those curly braces in a property definition. Those are only required in the content of a tag and not an attribute.
I'm also not sure what the -> arrow is supposed to be doing. Is that from PHP?
You probably want something more like the following:
<div class="container">
<page-profile :user="$user.toJson()"></page-profile>
</div>

How come my audio tag loads the source when it is hard coded, but not when provided by Vue.js?

I've created a component, and in that component is an audio tag. I've been trying to pass the file path of the mp3 to the component from its parent, but the audio element doesn't seem to be able to load the file successfully for some reason. The element just ends up greyed out, and I don't get any kind of error. I know the properties are being passed successfully because I'm also passing in the title of the track, and that loads just fine in the component. However, if I hardcode the same path in for the source, then it works fine. Which isn't a big deal, but I have 11 tracks to do and it would be much easier with a v-for statement.
Here's my parent component:
<template>
<div container>
<div class=banner>
<img src="../assets/CryptoLogo2.svg"/>
</div>
<div class="albumcontainer">
<div class="covercontainer"><img src="../assets/TIADCover(Final).png"/></div>
<div class="arrow"></div>
<div class="tracklistcontainer">
<table class="tracklist">
<tr class="track" v-for="track in tracks" :key="track.file" style="padding: 20px;">
<td>
<player :name="track.name" :file="track.file" />
</td>
</tr>
</table>
</div>
</div>
<div class="bio">
<p>
</p>
</div>
<div class="footer">
<div class="footericons">
<img src="../assets/icons/Facebook.svg" />
<img src="../assets/icons/Instagram.svg" />
<img src="../assets/icons/Twitter.svg" />
</div>
</div>
</div>
</template>
<script>
import player from './player'
export default {
name: "Album1",
data() {
return {
tracks: [
{
name: '5G',
file: '../assets/tracks/5G.mp3'
}
]
}
},
components:{
player
}
};
</script>
And then the component with the audio tag:
<template>
<div>
<h3>{{ name }}</h3>
<audio controls controlsList="nodownload">
<source ref="player" v-bind:src="file" type="audio/mpeg">
</audio>
</div>
</template>
<script>
export default{
name: "player",
props: {
name: {
type: String,
default: null
},
file:{
type: String,
default: null
}
}
}
</script>
Here you can see that my data is being passed successfully.
And you see that the source attribute is being loaded correctly as well.
This is what I get, and you can see the h3 loads fine, so I know the data is being passed. But the element is greyed out.
To test it out, I tried just hard coding the file path:
<source src="../assets/tracks/5G.mp3">
And that works just fine:
But I don't want to do it like that because I have about 11 tracks to do, so I would like for it to load from the data being passed so that I can reuse the component. Any ideas?
try using something like this in your Vue v-for I use this when src is not loading on the img tag.
:src="getSrc(x.src)"
methods: {
getSrc(src) {
return require("../assets/" + src);
}
}
Your watch function on file prop is never triggered because your file name is static, it never triggers a change on the watcher, before mounting the component, at least in the code you provided it is that way.
From Vue.js API vm.$watch
Watch an expression or a computed function on the Vue instance for changes. The callback gets called with the new value and the old value.

Vue.js referenced template components stop after first sub component

I'm attempting to create components using Vue, so that I can remove a lot of duplicated HTML in a site I'm working on.
I have a <ym-menucontent> component, which within it will eventually have several other components, conditionally rendered.
While doing this I've hit a wall and so have simplified everything to get to the root of the problem.
When rendering the ym-menucontent component the first sub-component is the only one which gets rendered and I can't work out why or how to get around it...
<template id="menucontent">
<div>
<ym-categories :menuitem="menuitem"/>
<ym-rootmaps :menuitem="menuitem"/>
<p>1: {{menuitem.rootMapsTab}}</p>
<p>2: {{menuitem.exploreTab}}</p>
</div>
</template>
<template id="rootmaps">
<div>Root Maps</div>
</template>
<template id="categories">
<div>Categories</div>
</template>
app.js
Vue.component('ym-menucontent', {
template: '#menucontent',
props: ['menuitem'],
data: function() {
return {
customMenu: window.customMenuJSON
}
}
});
Vue.component('ym-rootmaps', {
template: '#rootmaps',
props: ['menuitem'],
data: function() {
return {
customMenu: window.customMenuJSON,
rootMaps: window.rootAreas
}
}
});
Vue.component('ym-categories', {
template: '#categories',
props: ['menuitem'],
data: function() {
return {
customMenu: window.customMenuJSON,
rootMaps: window.rootAreas
}
}
});
usage...
<div
v-for="mi in customMenu.topLevelMenuItems"
:id="mi.name"
class="page-content tab swiper-slide">
<ym-menucontent :menuitem="mi"/>
</div>
Output
<div>Categories</div>
if I switch around ym-cateogries and ym-rootmaps then the output becomes...
<div>Root Maps</div>
if I remove both then I see...
<p>1: true</p>
<p>2:</p>
I'd expect to see a combination of all of them...
<div>Categories</div>
<div>Root Maps</div>
<p>1: true</p>
<p>2:</p>
This is probably because you're using self-closing components in DOM templates, which is recommended against in the style-guide ..
Unfortunately, HTML doesn’t allow custom elements to be self-closing -
only official “void” elements. That’s why the strategy is only
possible when Vue’s template compiler can reach the template before
the DOM, then serve the DOM spec-compliant HTML.
This should work for you ..
<template id="menucontent">
<div>
<ym-categories :menuitem="menuitem"></ym-categories>
<ym-rootmaps :menuitem="menuitem"></ym-rootmaps>
<p>1: {{menuitem.rootMapsTab}}</p>
<p>2: {{menuitem.exploreTab}}</p>
</div>
</template>
<div
v-for="mi in customMenu.topLevelMenuItems"
:id="mi.name"
class="page-content tab swiper-slide">
<ym-menucontent :menuitem="mi"></ym-menucontent>
</div>

Categories