"Subclass" a Riot.js template/custom element? - riot.js

With Riot.js, is there any provision for inheritance with custom elements?
As a trivial example, suppose I have a custom element <custom-button>. Something like this:
<custom-button>
<button>{innerContent}</button>
</custom-button>
Now, maybe I want to sub-class this button as a new custom element, perhaps something that includes an icon:
<custom-button-with-icon>
<inner-content>
{icon} {text}
</inner-content>
<script>
this.extends('custom-button');
</script>
</custom-button-with-icon>
Is there anything like this in Riot.js that allows me to override part of an outer template, or otherwise subclass a custom element?

If you are using Riot.js v4, for subclassing the template/custom component you can use the slot functionality of Riot.js. You create the component with a slot field
<custom-button>
<button>
<slot/>
</button>
</custom-button>
Then you can create another component which uses the custom button
<custom-button-with-icon>
<custom-button>
{icon} {text}
</custom-button>
</custom-button-with-icon>
Then the slot would be replaced with {icon} {text} when the custom-button-with-icon component is used.
More info here: https://riot.js.org/api/#slots

Related

Slot for wrapper content in Vue

Imagine that you have a user list component UserList that shows users in scrollable view. Each users details are represented with UserDetails component wrapped with Card component.
UserList
<template>
<Card>
<UserDetails />
</Card>
<Card>
<UserDetails />
</Card>
...
</template>
Now imagine that you're using this UserList component and want to re-implement the wrapper for UserDetails without modifying it's contents.
Adding a slot would work for replacing the wrapper but everything inside the wrapper would need to be re-implemented as well.
It would be nice if we could write Vue like this:
UserList
<template>
<slot name="wrapper">
<Card>
<template #content>
<UserDetails />
</template>
</Card>
</slot>
...
</template>
Consuming component:
<UserList>
<template #wrapper>
...
<NewImplementation>
<slot :name="content" />
</NewImplementation>
</template>
</UserList>
It would work by using slots in "reverse" way of how we're used to.
This isn't valid syntax but I bet someone else has thought about the same problem. The need to replace some content with slot but not all of it.
Wrapper component could be given as property but I think it's not the correct solution because we have slots to avoid doing just that.
Are there any good solutions?
If I understood right, what you're looking for is Teleports (previously called Portals) which is only available in version 3.0
What you want is problematic. Vue has no direct support for something like that.
Let me establish some naming conventions before I try to explain why:
Child - UserList component
Parent - component consuming Child
Main problem in this scenario is that slot content is completely generated in the consuming component and just passed to a Child component using
$slots - this is for normal slots. $slots.wrapper is just a static array of VNodes
$scopedSlots - for scoped slots. $scopedSlots.wrapper is a function. When Child renders it calls that function and renders returned an array of VNodes
(Note: In Vue 3 it is just $slots. All slots are functions)
What that means is that Child component has no way to somehow change the content of the slot (e.g. include something in the middle). It can pass some data into it (slot scope) but that's all...
Possible workarounds:
1. Just pass a wrapper component as a prop
Wrapper component could be given as property but I think it's not the correct solution because we have slots to avoid doing just that.
Well not really. Slots have a different purpose. If you want to change the wrapper and let the rest of the Child intact, this is pretty good and simple solution
2. Give a Parent the component it should render inside the wrapper using slot scope
This is something very well demonstrated in route-view component of Vue Router 4.x - see the docs
<UserList v-slot="{ UserComponent, UserData }">
<NewWrapper>
<component :is="UserComponent" v-bind:data="UserData" />
</NewWrapper>
</UserList>
So you have <component :is="UserComponent" /> instead of <UserDetails />. This is useful as the UserList component can now control what component is rendered inside the wrapper (and even change it dynamically)

Vuejs How to pass parent classes to child component in template

I'm trying to remember how to pass a parent's :class bindings to a specific child component within a template. For instance:
// parent-component.vue
<template>
<child-component :class="['foo', bar, 'baz']">
</template>
// child-component.vue
<template>
<div class="dont-want-classes-here">
<h1 class="not-here-either">Someting v Important</h1>
<sub-component :class="['want-parent-classes in-here', ...$parent.classes]">
</div>
</template>
Do I need to create a new prop just for that purpose? is there a specific part of the Vue instance I can access from within the component?
Thanks
Do I need to create a new prop just for that purpose?
Yes. Vue does not provide a way to customize how the class and style props are applied to the template. It will always apply them to the root element and you cannot change this.
However if it were a functional component, then you can do this. But that doesn't apply here.
Is there a specific part of the Vue instance I can access from within the component?
You can access the class directly from the vnode:
this.$vnode.data.staticClass // for class="static"
this.$vnode.data.class // for :class="dynamic"

How get one single component (carousel) from element-ui if is imported as global config object?

Hi in project i have included element-ui.
In app.js:
import Element from 'element-ui'
and after:
Vue.use(Element, {locale})
So in my single file components template i can use carousel like this: (In this component is not any initialization carousel via vue like import, parent whatever...)
<el-carousel>
<el-carousel-item>
<p>hello!</p>
</el-carousel-item>
</el-carousel>
And it works... BUT. How i can access to this element? Look to actual slide, use events and so on....?
I was tried in component something like:
import { Carousel } from 'element-ui';
and add to components list... It works, but it is another instance of this class...
So how i can GET real instance of carousel from DOM?
Easy. Make reference on element via ref attribute like this:
<el-carousel ref="myreference"></el-carousel>
and after this access:
console.log(this.$refs.myreference);

is it correct global component communication in vue?

i make modal popup components myPopup.vue for global.
and import that in App.vue and main.js
i use this for global, define some object Vue.prototype
make about popup method in Vue.prototype
like, "show" or "hide", any other.
but i think this is maybe anti pattern..
i want to find more best practice.
in App.vue
<div id="app>
<my-popup-component></my-popup-conponent>
<content></content>
</div>
main.js
...
Vue.prototype.$bus = new Vue(); // global event bus
Vue.prototype.$popup = {
show(params) {
Vue.prototype.$bus.$emit('showPopup', params);
},
hide() {
Vue.prototype.$bus.$emit('hidePopup');
}
}
Vue.component('my-popup-component', { ... });
...
myPopup.vue
....
export default {
...
created() {
this.$bus.$on('showPopup', this.myShow);
this.$bus.$on('hidePopup', this.myHide);
}
...
need-popup-component.vue
methods: {
showPopup() {
this.$popup.show({
title: 'title',
content: 'content',
callback: this.okcallback
});
}
}
It seems to be works well, but i don't know is this correct.
Is there any other way?
I was very surprised while reading your solution, but if you feel it simple and working, why not?
I would do this:
Add a boolean property in the state (or any data needed for showing popup), reflecting the display of the popup
use mapState in App.vue to bring the reactive boolean in the component
use v-if or show in App.vue template, on the popup declaration
create a 'showPopup' mutation that take a boolean and update the state accordingly
call the mutation from anywhere, anytime I needed to show/hide the popup
That will follow the vue pattern. Anything in state, ui components reflect the state, mutations mutates the state.
Your solution works, ok, but it doesn't follow vue framework, for exemple vue debug tools will be useless in your case. I consider better to have the minimum of number of patterns in one app, for maintenance, giving it to other people and so on.
You somehow try to create global component, which you might want to consume in your different projects.
Here is how I think I would do this -
How do I reuse the modal dialog, instead of creating 3 separate dialogs
Make a separate modal component, let say - commonModal.vue.
Now in your commonModal.vue, accept single prop, let say data: {}.
Now in the html section of commonModal
<div class="modal">
<!-- Use your received data here which get received from parent -->
<your modal code />
</div>
Now import the commonModal to the consuming/parent component. Create data property in the parent component, let say - isVisible: false and a computed property for the data you want to show in modal let say modalContent.
Now use it like this
<main class="foo">
<commonModal v-show="isVisible" :data="data" />
<!-- Your further code -->
</main>
The above will help you re-use modal and you just need to send the data from parent component.
How do I know which modal dialog has been triggered?
Just verify isVisible property to check if modal is open or not. If isVisible = false then your modal is not visible and vice-versa
How my global dialog component will inform it's parent component about its current state
Now, You might think how will you close your modal and let the parent component know about it.
On click of button trigger closeModal for that
Create a method - closeModal and inside commonModal component and emit an event.
closeModal() {
this.$emit('close-modal')
}
Now this will emit a custom event which can be listen by the consuming component.
So in you parent component just use this custom event like following and close your modal
<main class="foo">
<commonModal v-show="isVisible" :data="data" #close- modal="isVisible = false"/>
<!-- Your further code -->
</main>

How to add a click function to an imported component in Vue

So I have a Vue2 app. I have create a component "u-button"
when i import this and use it in another component, I want to be able to add a click function to it. However at the moment it looks for a function on the u-button component rather than the component it is being used in.
so for example, in the below if i click the first button nothing happens, if i click the second button i get the console log.
<template>
<div>
<u_button #click="clicked">Click me</u_button>
<button #click="clicked">Click me</button>
</div>
</template>
<script>
import u_button from '../components/unify/u_button'
export default {
components: {
u_button
},
methods: {
clicked() {
console.log("Working!");
}
}
}
</script>
However if i add a method on the u-button component, then it calls that. So how can i get my below example to work ? The only thing I can think of is to wrap it in another div and add the click function to that. but I'm wondering if there is a better way?? I dont want to use events to do this either as that gets messy very quickly.
As you can imagine having a reusable button that when clicked always performs the same function is a bit pointless.
It's because of the nature of components for example if we had a (virtual) iframe component which had a button in it and we'd like to detect click event on it we might name the event click and listen for it in the parent component; therefore, Vue introduced a feature called event modifiers for example in Vue, We have .native modifier (you can read more about the Vue modifiers here)
Now, to make your code work, You should add a .native after #click like this:
<u_button #click.native="clicked">Click me</u_button>
By the way, it's better to develop a naming convention for yourself It'd become handy when your projects get larger.