Riotjs - mount tags dynamically - riot.js

How can I mount tags dynamically? This is what I have:
my-app.tag.html
<my-app id="mounting_point"></my-app>
nav-menu.tag.html
<nav-menu>
<div class="menu_page">
<div class="menu_page__items">
<a onclick="{goTo}" class="menu_page__item" data-page_link="home-page">Home</a>
<a onclick="{goTo}" class="menu_page__item" data-page_link="about-page">About</a>
<a onclick="{goTo}" class="menu_page__item" data-page_link="contact-page">Contact</a>
</div>
</div>
<script>
export default {
goTo(e) {
let next_page = e.target.dataset.page_link;
riot.mount("#mounting_point", {}, next_page)
}
}
</script>
</nav-menu>
When I click on 'Home' the content of the home-page tag gets appended to the #mounting_point container, but when I click on another link the content of the mounting point is not replaced, but appended.
Thank you for any suggestions

Unmount the tag at the DOMNode before mounting the tag to it.
Pass riot.unmount the selector to unmount and an option to keep the parent.
goTo(e) {
let next_page = e.target.dataset.page_link;
riot.unmount('#mounting_point', true);
riot.mount('#mounting_point', {}, next_page);
}

Related

How to pass the src attribute of the img tag to the child component through the parent component in vue

I have a parent component and a child component, and I want to pass a src attribute to the child component via defineProps to make it display the image.
Below is my parent component code:
<script setup>
import item from "./item.vue";
const items=[
{
id:0,
img:'../assets/2.png',
name:'秘塔写作猫',
des:'码文章必备工具'
},
{
id:1,
img:'../assets/2.png',
name:'AdblockPlus广告拦截',
des:'最流行的广告拦截拓展程序'
},
{
id:2,
img:'../assets/3.png',
name:'Tampermonkey油猴脚本',
des:'码文章必备工具'
},
{
id:3,
img:'../assets/4.png',
name:'蔚蓝主页',
des:'个性化简洁风格浏览器主页'
},
{
id:4,
img:'../assets/5.png',
name:'ABCD PDF',
des:'完全免费在线PDF压缩转换工具'
},
{
id:5,
img:'../assets/6.png',
name:'ASO谷歌商店工具',
des:'洞悉竞品下载、评论等核心数据'
},
]
</script>
<template>
<div id="ain">
<div id="head">
<h4>站长推荐</h4>
<a>查看全部</a>
</div>
<div id="body">
<item v-for="item in items" :key="item.id" :img="item.img" :name="item.name" :des="item.des">
</item>
</div>
</div>
</template>
Below is my subcomponent code:
<script setup>
defineProps(['img','name','des'])
</script>
<template>
<div id="item">
<div>
<img src="img">
<p>{{name}}</p>
<p id="detail">{{des}}</p>
</div>
</div>
</template>
This error is displayed in the console:GET http://127.0.0.1:5173/assets/2.png 404 (Not Found)
I think it's a problem with passing parameters, but I don't know how to modify it, thank you all.
There are two solutions
1.You can use an absolute path
2.Put your image in the public path under the root directory
It looks like you are using Vite and there are a few options to reference images:
Use import statements for images to obtain the image URL
import imgUrl from './assets/img.png'
document.getElementById('hero-img').src = imgUrl
Place images inside public folder and reference using /. For example if all images are contained inside public/img then the URL would be /img.
Try new URL(url, import.meta.url) although this does not work with SSR
Number 2) is probably the easiest if these images won't be updated.
Reference:
https://vitejs.dev/guide/assets.html

how to redirect to specific component in vue js

I want to redirect inside a URL without page refresh, without using a router link as below :
<router-link to="/about us " active-class="active">foo</router-link>
I want to print routes like below:
<li class="nav-item phone">
<a class="nav-link" href="contact-us.html">
اتصل بنا
</a>
</li>
My route:
const routes = [
{ path: '/aboutus/', component: AboutUs }
]
Try this
this.$router.push('about')
You may need a workaround for this.
This solution won't change the url either :)
Set an html in the data
data: () => {
return {
html: null
}
}
Get the content of your html file using any request and assign to the html in data section. You can fetch this from any life cycle hook. Here I'm using beforeMount.
beforeMount() {
this.fetchAllEmployees();
axios.get('contact-us.html')
.then(response => {
this.html = response.data;
})
}
Now you can show the html content in your component like this
<template>
<div>
<div v-html="html"></div>
</div>
</template>
To show only when clicking the a tag, you can add another variable in the data which can be used to toggle the value.

Vue component not rendered on second visit

I have a Vue component that lists a bunch of clickable tags. When you click on a tag, it takes you to another page with a list of objects containing that tag.
The relevant parts of the component code are:
<template>
<div>
<h2>All Tags</h2>
<TagList v-bind:tags="tags"/>
</div>
</template>
...
<script>
import TagList from './TagList'
export default {
name: 'AllTags',
components: {
TagList
},
data () {
return {
tags: []
}
},
mounted () {
tags = // array loaded from a database
}
}
</script>
This all works fine when I initially view the page. However if I browse away from this list, e.g. by clicking on a single tag, and then browse back, I only see the <h2>All Tags</h2> header. Using the Vue debugger in the browser, I can see that the data are still there.
I'm using <router-view :key="$route.fullPath"> to control the overall app and suspect the problem lies with the keys somehow.
Can someone point me in the right direction here? How can I get the TagList component to render every time I visit that page of the app?
EDIT: Here's the code of the TagList component:
<template>
<div class="tags">
<Tag v-for="tag in tags" v-bind:tag="tag" v-bind:key="tag" />
</div>
</template>
<script>
import Tag from './Tag'
export default {
name: 'TagList',
props: ['tags'],
components: {
Tag
}
}
</script>
You can try removing v-bind all thought its not required to use, I've checked your code it seems to work fine after visiting a tag and going back, all tags are still rendered. You can take a look at this working sample .
https://codesandbox.io/s/vue-template-3tcs4?fontsize=14

How to stop <router-link> from sending the user to another page?

I have a logic like this: is the user is V2 use the user to the url in subHeaderRouter.router. If the user isn't launch this.openModal:
<router-link
v-for="subHeaderRouter in subHeaderRouters"
:to="subHeaderRouter.router"
#click="handleOpenModal()">
</router-link>
handleOpenModal () {
if (this.IsV2User) return
this.openModal('changeUserType', 'user.changeUserType')
}
The only thing I need to do now is to stop :to then she user is not V2. How to accomplish that?
You can prevent the default <router-link> behavior by specifying no default event to listen to and handling the click event manually with the .native modifier:
<router-link
v-for="subHeaderRouter in subHeaderRouters"
event=""
:to="subHeaderRouter.router"
#click.native.prevent="handleOpenModal(subHeaderRouter.router)"/>
handleOpenModal(route) {
if (this.IsV2User) {
this.$router.push(route)
} else {
this.openModal('changeUserType', 'user.changeUserType')
}
}
If the event="" seems weird to you, it also works with an empty attribute:
<router-link
v-for="subHeaderRouter in subHeaderRouters"
event
:to="subHeaderRouter.router"
#click.native.prevent="handleOpenModal(subHeaderRouter.router)"/>
Vue 3 Solution
In Vue3, the event has been removed from "<router-link>, you will need to use v-slot API instead.
<router-link :to="yourRoute" custom v-slot="{ href, navigate }">
<a v-if="yourBoolean" #click="handleEvent()">Run the function</a>
<a
v-else
:href="href"
#click="navigate">
Go to route in "to"
</a>
</router-link>
// Vue 3 Composition API
export default {
setup() {
const handleEvent = () => {
// Add your logic here
}
}
};
I had the same problem and used dynamic Vue Components as solution. You will also have to check the difference in styling for router and div tag.
<component
:is=" condition ? 'router-link' : 'div' "
#click="handleOpenModal"
:to="your_link">
</component>

Conditionally rendering parent element, keep inner html

Is there any built-in way to go about conditionally showing a parent element?
To illustrate:
<a v-show-but-keep-inner="someCondition">
<span>This is always rendered no matter what</span>
</a
I think it's a job for custom directive. I made this one as a quick POC:
Vue.directive('showButKeepInner', {
bind(el, bindings) {
bindings.def.wrap = function(el) {
// Find all next siblings with data-moved and move back into el
while (el.nextElementSibling && el.nextElementSibling.dataset.moved) {
el.appendChild(el.nextElementSibling).removeAttribute('data-moved')
}
el.hidden = false
}
bindings.def.unwrap = function(el) {
// Move all children of el outside and mark them with data-moved attr
Array.from(el.children).forEach(child => {
el.insertAdjacentElement('afterend', child).setAttribute('data-moved', true)
})
el.hidden = true
}
},
inserted(el, bindings) {
bindings.def[bindings.value ? 'wrap' : 'unwrap'](el)
},
update(el, bindings) {
bindings.def[bindings.value ? 'wrap' : 'unwrap'](el)
}
})
new Vue({
el: '#app',
data: {
someCondition: false
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.min.js"></script>
<div id="app">
<p>
<button v-on:click="someCondition = !someCondition">{{ someCondition }}</button>
</p>
<a v-show-but-keep-inner="someCondition" href="/">
<span>This is always rendered no matter what</span>
</a>
</div>
For Vue v3.x, the following would work:
<component
:is="condition ? 'custom-component' : 'slot'"
custom-component-prop
...
>
...
</component>
For Vue v2.x, a workaround is to do:
<component
:is="condition ? 'custom-component' : 'v-div'"
custom-component-prop
...
>
...
</component>
// VDiv.vue
<template>
<div>
<slot></slot>
</div>
</template>
<script>
export default {
inheritAttrs: false,
}
</script>
The tradeoff is there will be an extra element like div being rendered, since Vue v2.x doesn't support fragment.
I just ran into the same Problem.
Vue.js Core team member LinusBorg provides a great solution for this use case using a functional component with a custom render function:
Vue.component('with-root', {
functional: true,
props: ['show'],
render(h, ctx) {
const children = ctx.children.filter(vnode => vnode.tag) // remove unnecessary text nodes
// console.log(children)
if (children.length !== 1) {
console.warn('this component accepts only one root node in its slot')
}
if (ctx.props.show) {
return children[0]
} else {
return children[0].children
}
}
})
new Vue({
el: '#app',
data: {
show: true
}
})
<script src="https://unpkg.com/vue#2.6.14/dist/vue.js"></script>
<div id="app">
<with-root v-bind:show="show">
<a href="#">
<span>This is always rendered no matter what</span>
</a>
</with-root>
<br>
<button #click="show = !show">Toggle</button>
<pre>{{$data}}</pre>
</div>
His fiddle: https://jsfiddle.net/Linusborg/w9d8ujn8/
Source:
https://forum.vuejs.org/t/conditionally-render-parent-element/9324/2
If someone happens to be using the vue-fragment (https://www.npmjs.com/package/vue-fragment) library, the following works:
<component :is="someCondition ? 'a' : 'fragment'">
<span>This is always rendered no matter what</span>
</component>
That being said, I do not recommend to use one library just to do this. But if you already do, it can be useful.
Without Vuejs or other framework context, solely DOM.
You can't remove a DOM element without removing its children too.
What you could do is get the children of a DOM element and replace them with the parent or something similar like that.
With Vuejs you may be able to hide this functionality behind a directive or component, but I think this would be overcomplicating what you want to achieve.
If you want your anchor not to be clickable in certain cases, you could do something like v-on:click.prevent="yourCondition && xxx()". On top of that you could use css classes to hide the fact that it's still an anchor v-bind:class="{ fakeAnchor: yourCondition}".
Though the simplest solution may be to just duplicate your html.
<a v-show="someCondition">
<span>This is always rendered no matter what</span>
</a>
<span v-show="!someCondition">This is always rendered no matter what</span>
The best solution depends on what your case is. If the real inner content will be much larger it might not be okay to duplicate that. If that's the case you could encapsulate that in another vue component.
Maybe this approach helps you (using 'is'):
<template lang="pug">
component(is=someCondition?"v-show-but-keep-inner":"my-another-component")
v-form
v-layout
v-flex
v-btn(#click="doThat")
</template>
This way parent component changes depending on 'someCondition' and children are the same for both conditions.