Vue.js Import Objects - javascript

I'm facing a problem with importing Objects from the App.vue file to a component. But first I should explain the purpose of this project.
There's a component (navigation-drawer) and an App.vue file. The Navigation drawer has vue props in it, which you can dynamically change in the App.vue file. The problem with that is that I can only use as many links as there are in the Navigation-Drawer file.
I would like to edit it so I can use as many links as I need, without even having to open the Navigation-Drawer.vue file. Before I go into more detail, here are the files with the props & limited amount of links:
App.vue
<template>
<div id="app">
<navigation-drawer
name1="TFBern"
name2="Stackoverflow"
name3="YouTube"
name4="Google"
link1="https://vuejs.org"
link2="https://stackoverflow.com"
link3="https://youtube.com"
link4="https://google.com"
/>
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
import NavigationDrawer from './components/Navigation-Drawer.vue'
export default {
name: 'App',
components: {
HelloWorld,
NavigationDrawer
}
}
</script>
Navigation-Drawer.vue
<template>
<div class="navigationdrawer">
<span #click="openNav" style="fontsize:30px;cursor:pointer;display:flex;justify-content:center;">☰</span>
<div id="mySidenav" class="sidenav">
×
<a v-bind:href="link1">{{ name1 }}</a>
<a v-bind:href="link2">{{ name2 }}</a>
<a v-bind:href="link3">{{ name3 }}</a>
<a v-bind:href="link4">{{ name4 }}</a>
</div>
</div>
</template>
<script>
export default {
name: 'NavigationDrawer',
props: {
name1: String,
name2: String,
name3: String,
name4: String,
link1: String,
link2: String,
link3: String,
link4: String
},
methods: {
openNav() {
document.getElementById('mySidenav').style.width = '15%'
},
closeNav() {
document.getElementById('mySidenav').style.width = '0%'
}
}
}
</script>
Now, what I had in mind was to create a js object, which can import the links from App.vue into the Drawer. Something like this:
<navigation-drawer links="[ {title="Google", link="www.google.ch"} , {title="Youtube", link="www.youtube.com"} , {title=…, link=…} ]"
I don't really know how to do it... Can anyone help?
Thank you.

You are pretty close to the answer already. Change = to :, the values to be surrounded with ' instead of " so you have a list of objects
<navigation-drawer v-bind:links="[ {title:'Google', link:'www.google.ch'} , {title:'Youtube', link:'www.youtube.com'} , {title:…, link:…} ]"
Then the navigation-drawer props look like:
props: {
links: Array
},
and the html loops through the links with a v-for and template:
<div class="navigationdrawer">
<span #click="openNav" style="fontsize:30px;cursor:pointer;display:flex;justify-content:center;">☰</span>
<div id="mySidenav" class="sidenav">
×
<template v-for=v-for="(link, index) in links">
<a v-bind:href="link.link" :key="index">{{ link.title}}</a>
</template>
</div>
</div>

Related

Dynamic image path not working in Require function : This relative module was not found

I am stuck on an error from the past 2 days on VueJs require function. I am trying to past a prop to the Home component and then display the image.
Home.vue
<template>
<BlogPost :post="welcomeScreen"/>
<BlogPost :post="post" v-for="(post,index) in sampleBlogPost" :key="index"/>
</template>
<script>
import BlogPost from '../components/BlogPost.vue'
export default {
name: 'Home',
components:{
BlogPost
}
,
data(){
return{
welcomeScreen:{
title:'Welcome',
blogPost:'Test post',
welcomeScreen: true,
photo: 'coding',
}
,
sampleBlogPost: [
{
title: 'This is a filter title',
blogHtml : 'This is a blog filter title',
blogCoverPhoto: 'beautiful-stories'
}
,
{
title: 'This is a filter title',
blogHtml : 'This is a blog filter title',
blogCoverPhoto: 'designed-for-everyone'
}
]
}
}
}
</script>
<style lang="scss" scoped>
.logo{
max-width: 150px;
}
</style>
BlogPost.vue
<template>
<div class="blog-wrappe">
<div class="blog-content">
<h2 v-if="post.welcomeScreen">{{post.title}}</h2>
<h2 v-else>{{post.title}}</h2>
<p v-if="post.welcomeScreen">{{post.blogPost}}</p>
<p v-else>{{post.blogHtml}}</p>
<router-link class="link link-light" v-if="post.welcomeScreen" to="#">Login/Register <img src="#/assets/Icons/arrow-right-light.svg"></router-link>
<router-link class="link link-light" v-else to="#">View the post</router-link>
<div class="blog-photo">
<img v-if="post.welcomeScreen" :src="require('../assets/blogPhotos/${post.photo}.jpg')" alt="">
<!-- <img v-else :src="require('../assets/blogPhotos/${post.blogCoverPhoto}.jpg')" alt=""> -->
</div>
</div>
</div>
</template>
<script>
export default {
name: 'BlogPost',
props:['post'],
data(){
return {
photo: this.post.photo
}
}
}
</script>
<style lang="scss">
</style>
I am getting this error:
This relative module was not found:
../assets/blogPhotos/${post.photo}.jpg in ./node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/babel-loader/lib!./node_modules/vue-loader-v16/dist/templateLoader.js??ref--6!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader-v16/dist??ref--0-1!./src/components/BlogPost.vue?vue&type=template&id=a2b18adc"
But when I remove '${post.photo}' and placed 'coding' it works fine.
The following code will resolve the string inside require to the string you want it to. As far as I am concerned it will not work most of the time, since if the full image path is different, require will not know what files to load.
But since you are lucky and your folder's path is constant require will load all the files from that folder ('assets/blogPhotos') and you can use them dynamically.
<template>
<div class="blog-wrappe">
<img v-if="post.welcomeScreen" :src="require(`../assets/blogPhotos/${post.photo}.jpg`)" alt="">
</div>
</template>
<script>
export default {
name: 'BlogPost',
props: ['post'],
data() {
return {
photo: this.post.photo || 'cover',
}
}
}
</script>
Also if you have assets that have completely different paths, you can do something like this, this also has the added benefit, but it will have the added benefit of not loading unnecessary files from that folder, only the ones you need!
Take a look:
<template>
<div class="blog-wrappe">
<img v-if="post.welcomeScreen" :src="getPhotoUrl(post.photo)" alt="">
</div>
</template>
<script>
import coding from '../asset/blog/coding.jpg';
import coding1 from '../pictures/photos/coding1.jpg';
import coding2 from '../images/some/coding2.jpg';
import coding3 from '../browse/image/coding3.jpg';
export default {
name: 'BlogPost',
props: ['post'],
methods: {
getPhotoUrl(photoKey) {
const availablePhotoUrlsMap = {
coding,
coding1,
coding2,
coding3
}
if (!availablePhotoUrlsMap.hasOwnProperty(photoKey)) {
throw new Error(`photo with key ${photoKey} is not available`);
}
return availablePhotoUrlsMap[photoKey];
}
}
</script>

Reusable Alpine.js components?

How do I use Alpine.js to create a reusable component and display it? E.g., maybe I want to define an generic Alpine.js button component that changes text and color from parameters, then have my Alpine.js navbar component use the button component to show a login button.
Can I do this in pure client-side code, without relying on a server templating out all of the button HTML everywhere the button component is used?
Can I do this in pure client-side code, without relying on a server templating?
Yes, you can.
Alpine.js always will try to persuade you to use a server side templating engine.
But just like you, I don't let myself be persuaded:
<template x-component="dropdown">
<div x-data="{ ...dropdown(), ...$el.parentElement.data() }">
<button x-on:click="open">Open</button>
<div x-show="isOpen()" x-on:click.away="close" x-text="content"></div>
</div>
</template>
<x-dropdown content="Content for my first dropdown"></x-dropdown>
<div> Random stuff... </div>
<x-dropdown content="Content for my second dropdown"></x-dropdown>
<x-dropdown></x-dropdown>
<script>
function dropdown() {
return {
show: false,
open() { this.show = true },
close() { this.show = false },
isOpen() { return this.show === true },
content: 'Default content'
}
}
// The pure client-side code
document.querySelectorAll('[x-component]').forEach(component => {
const componentName = `x-${component.getAttribute('x-component')}`
class Component extends HTMLElement {
connectedCallback() {
this.append(component.content.cloneNode(true))
}
data() {
const attributes = this.getAttributeNames()
const data = {}
attributes.forEach(attribute => {
data[attribute] = this.getAttribute(attribute)
})
return data
}
}
customElements.define(componentName, Component)
})
</script>
Alpine.js contributer #ryangjchandler remarks that reusable templates are out of scope for Alpine.js:
The proposed [Alpine.js version 3] x-component directive will NOT have anything to do with templating or the markup for your component. Instead it will provide a way of writing more immediately reusable data sets & functions, whilst reducing the amount of directives you need to define in your markup.
If you need re-usable templates, I would consider using a server-side template engine or a more monolithic front end framework such as Vue or React. (link)
and
The functionality you are looking for is far out of the scope of Alpine. It's designed to work alongside your existing markup from the server or static files, not replace / component-ise your markup. (link)
With Alpine.js v3 and Global Alpine Components, you can use Alpine.component() to encapsulate this functionality.
<div x-data="dropdown">
...
</div>
<script>
Alpine.component('dropdown', () => ({
open: false,
toggle() { this.open = !this.open }
}))
</script>
use alpinejs-component
one same page by cdn:
<div
x-data="{
people: [
{ name: 'John', age: '25', skills: ['JavaScript', 'CSS'] },
{ name: 'Jane', age: '30', skills: ['Laravel', 'MySQL', 'jQuery'] }
]
}"
>
<ul>
<template x-for="person in people">
<!-- use the person template to find the <template id="person"> element. -->
<x-component-wrapper x-component template="person" x-data="{ item: person }"></x-component-wrapper>
</template>
</ul>
</div>
<template id="person">
<li class="user-card">
<h2 x-text="item.name"></h2>
<p x-text="item.age"></p>
<ul>
<template x-for="skill in item.skills">
<li x-text="skill"></li>
</template>
</ul>
</li>
</template>
<script src="https://unpkg.com/alpinejs-component#1.x.x/dist/component.min.js"></script>
<script defer src="https://unpkg.com/alpinejs#3.x.x/dist/cdn.min.js"></script>
use url import html template:
<div
x-data="{
people: [
{ name: 'John', age: '25', skills: ['JavaScript', 'CSS'] },
{ name: 'Jane', age: '30', skills: ['Laravel', 'MySQL', 'jQuery'] }
]
}"
>
<ul>
<template x-for="person in people">
<x-component-wrapper x-component url="/public/person.html" x-data="{ item: person }"></x-component-wrapper>
</template>
</ul>
</div>
<script src="https://unpkg.com/alpinejs-component#1.x.x/dist/component.min.js"></script>
<script defer src="https://unpkg.com/alpinejs#3.x.x/dist/cdn.min.js"></script>
person.html:
<li class="user-card">
<h2 x-text="item.name"></h2>
<p x-text="item.age"></p>
<ul>
<template x-for="skill in item.skills">
<li x-text="skill"></li>
</template>
</ul>
</li>
install by npm :
npm i -D alpinejs-component
yarn add -D alpinejs-component
register plugin:
import Alpine from "alpinejs";
import component from "alpinejs-component";
Alpine.plugin(component);
window.Alpine = Alpine;
Alpine.start();
or use module in browser:
<x-component-wrapper x-component template="dropdown" x-data="dropdown"></x-component-wrapper>
<x-component-wrapper x-component template="dropdown" x-data="dropdown"></x-component-wrapper>
<template id="dropdown">
<div #click="close" class="dropdown-toggle">
<button x-on:click="open">Open</button>
<div x-show="show" x-text="content"></div>
</div>
</template>
<script type="module">
import { default as Alpine } from 'https://cdn.skypack.dev/alpinejs'
import alpinejsComponent from 'https://cdn.skypack.dev/alpinejs-component'
function dropdown() {
return {
show: false,
open() {
console.log('open')
this.show = true
console.log(this.show)
},
close(event) {
const button = this.$el.querySelector('button')
const target = event.target
if (this.$el.contains(target) && !button.contains(target)) {
this.show = false
}
},
get isOpen() {
return this.show === true
},
content: 'Default content',
init() {
console.log(this.$el.parentElement)
console.log('dropdown --- init')
},
}
}
Alpine.data('dropdown', dropdown)
Alpine.plugin(alpinejsComponent)
Alpine.start()
</script>
work well.
more info alpinejs-component
You can do this with Alpine.data and the documented approach for encapsulating directives with x-bind. The trick is to bind the x-html directive. In your HTML do this:
<div x-data="dropdown" x-bind="bind"></div>
In your Javascript:
document.addEventListener('alpine:init', () => {
Alpine.data('dropdown', () => ({
show: false,
bind: {
['x-html']() { return `
<button #click="show = !show">Click me!</button>
<div x-show="show">Hello World</div>
`},
},
}));
})
JSFiddle here.
It's a little hacky as you are encapsulating all your nested content in a multiline HTML string bound in the x-html directive (though perhaps no more hacky than the alternative of cloning templates everywhere). Make sure you don't use the backtick character in the content. Nevertheless, the content can be nested as deeply as you like and can contain Alpine.js directives. You can initialise your component by declaring and passing parameters into Alpine.data. You could also bind x-modelable to expose any properties of your component as outputs.
If you prefer to use templates, maybe because your editor does better syntax highlighting when markup is not embedded in a string, you can combine this approach with templates. Here's an example that demonstrates x-modelable and the use of templates too. In effect, Alpine does your template cloning for you.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script defer src="https://unpkg.com/alpinejs#3.x.x/dist/cdn.js"></script>
</head>
<body>
<div x-data="{clicked: false}">
<div>Clicked is <span x-text="clicked"></span></div>
<div x-data="dropdown" x-bind="bind" x-model="clicked"></div>
</div>
<template id="dropdown">
<button #click="show = !show">Click me!</button>
<div x-show="show">Hello World</div>
</template>
</body>
<script type="text/javascript">
document.addEventListener('alpine:init', () => {
Alpine.data('dropdown', () => ({
show: false,
bind: {
['x-modelable']: 'show',
['x-html']() { return document.querySelector('#dropdown').innerHTML},
},
}));
})
</script>
</html>
JSFiddle here.
The x-component with native custom element in Vimesh UI (https://github.com/vimeshjs/vimesh-ui) is a more complete reusable component implementation :
<head>
<script src="https://unpkg.com/#vimesh/style" defer></script>
<script src="https://unpkg.com/#vimesh/ui"></script>
<script src="https://unpkg.com/alpinejs" defer></script>
</head>
<body x-cloak class="p-2" x-data="{name: 'Counter to rename', winner: 'Jacky'}">
Rename the 2nd counter : <input type="text" x-model="name" class="rounded-md border-2 border-blue-500">
<vui-counter x-data="{step: 1}" :primary="true" title="First" x-init="console.log('This is the first one')" owner-name="Tom"></vui-counter>
<vui-counter x-data="{step: 5}" :title="name + ' # ' + $prop('owner-name')" owner-name="Frank"></vui-counter>
<vui-counter x-data="{step: 10, value: 1000}" :owner-name="winner">
<vui-counter-trigger></vui-counter-trigger>
</vui-counter>
<template x-component.unwrap="counter" :class="$prop('primary') ? 'text-red-500' : 'text-blue-500'"
x-data="{ step : 1, value: 0}" x-init="$api.init && $api.init()" title="Counter" owner-name="nobody">
<div>
<span x-text="$prop('title')"></span><br>
Owner: <span x-text="$prop('owner-name')"></span><br>
Step: <span x-text="step"></span><br>
Value : <span x-text="value"></span><br>
<button #click="$api.increase()"
class="inline-block rounded-lg bg-indigo-600 px-4 py-1.5 text-white shadow ring-1 ring-indigo-600 hover:bg-indigo-700 hover:ring-indigo-700">
Increase
</button>
<slot></slot>
</div>
<script>
return {
init() {
console.log(`Value : ${this.value} , Step : ${this.step}`)
},
increase() {
this.value += this.step
}
}
</script>
</template>
<template x-component="counter-trigger">
<button #click="$api.of('counter').increase()"
class="inline-block rounded-lg mt-2 bg-green-600 px-4 py-1.5 text-white shadow ring-1 ring-green-600 hover:bg-green-700 hover:ring-green-700">
Tigger from child element</button>
</template>
</body>

VUE- How do I put the values inside of components imported?

I remember I have seen once how to put the values in the html text area after importing components in VUE.
I'm not sure there is a way to do that or I just remember things in a wrong way.
my code is as below.
<template>
<div class="container">
<div class="row">
<Heading></Heading>
</div>
<hr>
<div class="row">
<div class="col-xs-12 col-sm-6">
<ul class="list-group">
<comp v-for='(value,index) in listing' :key='index'>{{value}}</comp>
</ul>
</div>
<serverstat></serverstat>
</div>
<hr>
<div class="row">
<footing></footing>
</div>
</div>
</template>
<script>
import Heading from './assets/Heading.vue';
import comp from './assets/comp.vue';
import serverstat from './assets/serverstatus.vue';
import footing from'./assets/footing.vue';
export default {
data() {
return {
listing: ['max','toms','judy','michael','dumdum']
}
},
components: {
Heading,comp,serverstat,footing
},
};
</script>
<style>
</style>
-comp-
<template>
<li class="list-group-item">
</li>
</template>
<script>
export default {
}
</script>
<style>
</style>
After I render this,
it doesn't show {{value}}. It only shows blank .
How do I insert the {{value}} within the html element?
Thank you in advance.
Since you are entering a value inside of a component, you can render it by using a slot in your component like this:
<template>
<li class="list-group-item">
<slot />
</li>
</template>
<comp v-for='(value,index) in listing' :key='index'>
<slot>{{ value }} </slot>
</comp>
Then in comp component use slot as
<slot/>
Not including the approach for props as you don't want to use that. Use the link above to learn more about slots.
When you use v-for it calls all the value from an array and :key='index' defines each object row from an array. If your object listing consists of firstname, lastname as your object then the value you want to print will be {{value.firstname}}. You are missing object name in value.
Can you try this once :
<comp v-for='(value,index) in listing' :key='index'>{{value.index}}</comp>

In Vue&Vuex, how can I activate onclick method in child component?

I'm trying to edit JS library that already existed but it consisted of Vue. So I studied Vue a little.
The problem is that I made child component called 'Analysis' and want to anchor function. I made tag and bind 'moveAnchor' method to onclick, also declared 'moveAnchor' on methods part. but it didn't work. How can I fix? I'm sorry for being inexperienced.. :(
it is script.js of analysis.
import { mapActions, mapState } from 'vuex';
export default {
name: 'Analysis',
computed: {
checkName : function(){
var flag = this.$store.state.analysis.name;
if(flag.indexOf("/PCs/") != -1){
console.log(flag);
}
}
},
methods: {
moveAnchor: function (id){
var div = document.getElementById(id).scrollIntoView();
}
it is template.html of analysis.
<div :class="$style.scrollarea">
<div :class="$style.dropdown">
<button :class="$style.dropbtn">Analysess</button>
<div :class="$style.dropContent">
<a v-for="item in analyData" v-bind:key="item.id" #onclick="moveAnchor(item.id)">
{{ item.title }}
</a>
</div>
</div>
<span>
{{ checkName }}
</span>
<div v-for="item in analyData">
<h1 v-bind:id="item.id">{{ item.title }}</h1>
<img v-bind:src="item.src" style="width: 100%; height: auto">
</div>
Welcome to StackExchange!
The correct binding for Vue's click event is v-on:click, or #click for shorthand. So when you write #onclick, Vue will never call that.
Just change #onclick to #click and all should work fine.

VueJS independent components events

I have a component that emit event through a bus as indicated below. The same component need to be included twice on another component. I want the events emitted to populate different variables;
//component.vue
<template>
<div>
Hello there?
<a #click="changed">New</a>
<ol>
<li v-for="option in list">
<div class='row justify-content-start'>
<div class='col-sm-6'><input v-model="option.value" type='text' placeholder="key"/></div>
<div class='col-sm-6'><input v-model="option.name" type='text' placeholder="Name"/></div>
</div>
</li>
</ol>
</div>
</template>
<script>
export default{
props:['options','iscolumn'],
data(){
return {list:this.options,item:{name:'',value:''}}
},
methods:{
changed(){
$bus.$emit('add-option',this.item,this.iscolumn);
}
}
}
</script>
/** root.vue **/
<template>
<div>
<h3>Rows</h3>
<div><rows :options="rows" :iscolumn="false"/></div>
<h3>Columns</h3>
<div><rows :options="columns" :iscolumn="true" /></div>
</div>
</template>
<script>
export default{
components:{'rows':require('./component')},
data(){
return {
columns:[],rows:[]
}
},
created(){
this.$bus.$on('add-option',(option,iscolumn)=>{
if (is_column) {this.columns.push(option);}
else this.rows.push(option);
})
}
}
</script>
When I click on the New from root both columns and rows get populated.
Looking for case where each of the component are independent, can't understand how they are sharing variables.
Any assistance will be appreciated.
Assign unique key attributes to the rows components:
<template>
<div>
<h3>Rows</h3>
<div><rows key="rows1" :options="rows" :iscolumn="false"/></div>
<h3>Columns</h3>
<div><rows key="rows2" :options="columns" :iscolumn="true" /></div>
</div>
</template>

Categories

Resources