Define and reuse inline component in Vue3 - javascript

Is it possible to define a "sub component" inline in Vue3 which can be reused in the same .vue file/component, but without actually defining a new .vue file (i.e. without defining this sub component as a new component).
The use case is that I have a very specific way of formatting <select> options which is used for multiple <select>s within the same component (.vue file), but which will not be used anywhere else (it's also small, so I am inclined to define this options formatting part inline). I don't necessarily want to copy and paste the formatting (and it would be good to keep it within the same .vue file because it's small).
I realise that this is only syntactic sugar which may or may not be relevant in specific cases (I'm also not seeking advice on whether or not this is a good idea). I'm just looking for a way this can be done (if not, that's also an answer ;-))

You can define h() function to create vnodes inside your setup script in vue 3.
for example:
<template>
<MyComponent />
</template>
<script setup>
const MyComponent = h('div', { class: 'bar', innerHTML: 'hello' })
</script>
vue document will help you completely. document

You could do something like this in Vue2
Vue.component('my-checkbox', {
template: '<div class="checkbox-wrapper" #click="check"><div :class="{ checkbox: true, checked: checked }"></div><div class="title">{{ title }}</div></div>',
data() {
return { checked: false, title: 'Check me' }
},
methods: {
check() { this.checked = !this.checked; }
}
});
Probably still works

Related

Vue Form Components and Conditional Rendering Overhead

I've seen and read a lot of posts on how best to handle forms. I know there's a lot of different opinions and that's not the point of this question. I'm still fairly new to Vue and have a pointed question regarding the framework in general rather that how to implement forms.
Due to numerous factors, we've decided the best way to go in our case is to create a generic FormField component with a prop of inputType. So we may have something like this:
<form-field input-type="text" :value="someValue"></form-field>
In the form component, we'll use <v-if="isText"> or similar. Obviously, there won't be a ton of if statements in this particular component (i.e. text, password, checkbox, etc) but I can't find any information on the overhead of conditional rendering.
Is there a significant amount of overhead in using conditional rendering over separating this out into a <my-checkbox-input>, <my-text-input>, <my-password-input>?
Well, as you said, there are many options to do it, I like to use the "Component" component (weird right?), This approach is kind of advanced, but I like it.
By the way, using AI (a lot of ifs) is not bad, because you are rendering what you need, just be sure to use v-if, v-else-if and v-else statements, ie:
<template>
<div>
<my-checkbox-input v-if="inputType === 'checkbox' />
<my-text-input v-else-if="inputType === 'text' />
<my-password-input v-else-if="inputType=== 'password' />
<input v-else />
<div>
</template>
<script>
export default {
props: {
inputType: {
type: String,
required: true
}
}
}
</script>
Above you can see a basic example, you should pass the required props or attributes to each input.
Now, this is what I use to have a group of components using the vue component "component":
<component
v-for="setting in additionalSettings"
:key="setting.feature"
:is="setting.component"
v-model="setting.enabled"
:class="setting.class"
:label="setting.label"
:description="setting.description"
:input-description="getEnableOrDisableWord(setting.enabled)"
:disabled="onRequest"
/>
To import the component(s):
export default {
name: "AdditionalSettingsContentLayout",
components: {
SelectInput: () =>
import(/* webpackChunkName: "SelectInput" */ "../components/SelectInput"),
},
}
I'm using this import syntax to add code splitting and lazy load (I think that is for that)
In this case, I'm using a json file as settings for the components:
settings: [
{
component: "SelectInput",
enabled: false,
label: "Toggle Personal Agenda feature on/off by event in ControlPanel",
feature: "ENABLE_MY_AGENDA",
class: "tw-mt-2 tw-mb-10",
},
{
component: "SelectInput",
enabled: false,
label: "Enable/disable Meeting scheduling by event",
feature: "ENABLE_MEETING_SCHEDULING",
class: "tw-mt-2 tw-mb-0 tw-mb-10",
},
{
component: "SelectInput",
enabled: false,
label: "Enable/disable Matchmaking by event",
feature: "ENABLE_MATCHMAKING",
class: "tw-mt-2 tw-mb-0",
},
],
I'm using Vuex to handle the change/update of the "enabled" state. The example above only uses the SelectInput component, but it could be any other component, only be sure to pass the required information for each field/input.
Be sure to do step by step changes or updates in your forms.
Read more about it here

Vuejs - Issue when removing a component via directives and the mounted/created event is being executed

I wanted my directive to work as v-if since in my directive I have to check access rights and destroy the element if it does not have access.
Here is my code
Vue.directive('access', {
inserted: function(el, binding, vnode){
//check access
if(hasAccess){
vnode.elm.parentElement.removeChild(vnode.elm);
}
},
});
vue file
<my-component v-access='{param: 'param'}'>
The issue is that i'm applying this directive to a component, it's removing the component but not the execution of functions called by the created/mounted hook.
In the component(my-component) there are functions in mounted/created hook. The execution of these functions are done and I don't want these functions to be executed. Is there a way to stop execution of the mounted/created events?
It is impossible to replicate the behavior of v-if in a custom directive. Directives cannot control how vnodes are rendered, they only have an effect on the DOM element it is attached to. (v-if is special, it's not actually a directive but instead generates conditional rendering code when the template is compiled.)
Though I would avoid doing any of the following suggestions if possible, I'll provide them anyway since it's close to what you want to do.
1. Extend the Vue prototype to add a global method
You definitely need to use v-if to do the conditional rendering. So all we have to do is come up with a global helper method which calculates the access permission.
Vue.prototype.$access = function (param) {
// Calculate access however you need to
// ("this" is the component instance you are calling the function on)
return ...
}
Now in your templates you can do this:
<my-component v-if="$access({ param: 'param' })">
2. Define global method in the root component
This is basically the same as #1 except instead of polluting the Vue prototype with garbage, you define the method only on the root instance:
new Vue({
el: '#app',
render: h => h(App),
methods: {
access(param) {
return ...
}
}
})
Now in your templates you can do this:
<my-component v-if="$root.access({ param: 'param' })">
Now it's clearer where the method is defined.
3. Use a global mixin
This may not be ideal, but for what it's worth you can investigate the viability of a global mixin.
4. Use a custom component
You can create a custom component (ideally functional but it needn't be) that can calculate access for specific regions in your template:
Vue.component('access', {
functional: true,
props: ['param'],
render(h, ctx) {
// Calculate access using props as input
const access = calculateAccess(ctx.props.param)
// Pass the access to the default scoped slot
return ctx.scopedSlots.default(access)
}
})
In your templates you can do this:
<access :param="param" v-slot="access">
<!-- You can use `access` anywhere in this section -->
<div>
<my-component v-if="access"></my-component>
</div>
</access>
Since <access> is a functional component, it won't actually render it's own component instance. Think of it more like a function than a component.
A bit overkill for your situation, but interesting nonetheless if you ever have a more complicated scenario.

Injecting a node into an external Vue component

I am a junior developer who lacks experience, so I apologize if my question showcases signs of sheer ignorance. My title may not be expressive of the problem I face, so I shall do my best to be descriptive.
In my project, I am making use of a 3rd party component (a dropdown menu), which I would like to modify in my application. I don't want to fork and edit their code since I would like to pull the latest styling changes since my modification is only slight, being that I would like to add some text next to the dropdown icon.
Here is a (simplified) version of the code.
<template>
<overflow-menu
ref="overflow_menu"
>
<overflow-menu-item
v-for="item in overflowMenuItems"
:id="item.id"
:key="item.name"
>
{{ item.tabName }}
</overflow-menu-item>
</overflow-menu>
</template>
<script>
import { OverflowMenu, OverflowMenuItem } from '#some-library/vue'; //Don't have control of the implementation of these components.
export default {
name: 'CustomOverflowMenu',
components: {
OverflowMenu,
OverflowMenuItem,
},
props: {
overflowMenuItems: Array,
label: String,
},
mounted() {
this.injectOverflowMenuLabel();
},
methods: {
injectOverflowMenuLabel() {
const overflowMenuElement = this.$refs.overflow_menu.$el.firstChild;
const span = document.createElement("span");
const node = document.createTextNode(this.$props.label);
span.appendChild(node);
overflowMenuElement.insertBefore(
span,
overflowMenuElement.firstChild,
);
}
}
};
</script>
It functionally works ok, however, it doesn't seem a particularly elegant solution, and I feel as if I could be doing it in a more "Vuey" way. I also am greeted with a Vue warning of: Error in created hook: "TypeError: Cannot read property 'insertBefore' of undefined. This ultimately means I am not able to mount my component and unit test my custom overflow menu.
Is there a way to get this functionality, but in a more maintainable manner. I would either like to simplify the logic of the injectOverflowMenuLabel function, or perhaps there is a completely alternative approach that I haven't considered.
Would appreciate any help, you lovely people.
Thanks,
Will.

preactjs seeing [Object object] instead of link

I am using preactjs to create my application. On top of this, I am using kendo grid. Inside the grid, I want to show a hyperlink. if the user clicks on the link, it should change the route. To render the link, i am using preact-router.
Here is the working fiddle.
let { h, render, Component } = preact;
// import { ... } from 'preact';
let { route, Router, Link } = preactRouter;
/** #jsx h */
class App extends Component {
componentDidMount() {
console.log('did mount !');
$("#grid").kendoGrid({
selectable: "multiple cell",
allowCopy: true,
columns: [
{ field: "productName",
template: function(e) {
return <link href="/">Home</link>
} },
{ field: "category" }
],
dataSource: [
{ productName: "Tea", category: "Beverages" },
{ productName: "Coffee", category: "Beverages" },
{ productName: "Ham", category: "Food" },
{ productName: "Bread", category: "Food" }
]
});
};
render({}, { }) {
return (
<div>
<h1>
Preact Kickstart
<sub>powered by preact</sub>
</h1>
<div id="grid"></div>
</div>
);
}
}
// Start 'er up:
render(<App />, document.body);
This is not even related to preact.
What you're doing is rendering a kendo grid using via jquery inside a preact component and using a preact component as a template.
One way to fix this is to return a html string:
template: function(e) {
const linkEl = $('<a>')
.attr('href', '#') // keep the a el but do not redirect to a different page on click
.text('Home')
.click((e) => {
e.preventDefault(); // prevent the original a tag behavior
route('/'); // this is using the `route` from preactRouter, which is already included at the top of the original file
});
return linkEl[0].outerHTML; // access the JS DOM element from jQuery element and get it's full html
}
I've also replaced the link (which is a typo, probably should've been Link) with a tag since there is no link element in basic html. Even though the name is wrong, this still works because the JSX transformer will interpret all the lowercase components as string name instead of using the component as a function (see how Babel compiles it). The transformer will generate h("link", { href: "/" }, "Home") and the h function returns an object which is then rendered as [Object object] because this is what happens when you try to convert to a string via .toString function. If preact would work in this case, it would render the actual <link href="/">Home</link> to the user, which would not have the desired behavior (except if a custom link component is defined somewhere else).
You can't return a preact component here because the kendo grid template is expecting a string template. One way would be to convert the preact component to a string, but I'm not sure that's even possible, I have never seen it done and you shouldn't have to.
Note: As said, you shouldn't have to be converting little parts of React or React-like to html. I would strongly advise against mixing preact code with jQuery which is rendering the kendo grid in your case. These two libraries are doing the rendering very differently. While jQuery is using the old approach of directly modifying the DOM and replacing the whole subtree, React (and all the implementation, such as preact) are rendering to virtual DOM and then have internal logic which figures out the difference with the actual DOM the user is seeing and display only the minimum difference so it makes the least updates required. With my quick google search, I have found react-kendo, but it doesn't seem very popular. There are also some blog posts from the kendo team themselves, but I haven't found any official support. If you want to use preact, try to find the (p)react way of doing it, for your example you could be using something like react-table (official demo). If on the other hand, you want to use powerful UI tools provided by kendo, you would be better off not adding the preact to the mix, it will make things more complex without much benefit or it could even make the whole thing worse.

How to add custom html attributes in JSX

There are different reasons behind it, but I wonder how to simply add custom attributes to an element in JSX?
EDIT: Updated to reflect React 16
Custom attributes are supported natively in React 16. This means that adding a custom attribute to an element is now as simple as adding it to a render function, like so:
render() {
return (
<div custom-attribute="some-value" />
);
}
For more:
https://reactjs.org/blog/2017/09/26/react-v16.0.html#support-for-custom-dom-attributes
https://facebook.github.io/react/blog/2017/09/08/dom-attributes-in-react-16.html
Previous answer (React 15 and earlier)
Custom attributes are currently not supported. See this open issue for more info: https://github.com/facebook/react/issues/140
As a workaround, you can do something like this in componentDidMount:
componentDidMount: function() {
var element = ReactDOM.findDOMNode(this.refs.test);
element.setAttribute('custom-attribute', 'some value');
}
See https://jsfiddle.net/peterjmag/kysymow0/ for a working example. (Inspired by syranide's suggestion in this comment.)
You can add an attribute using ES6 spread operator, e.g.
let myAttr = {'data-attr': 'value'}
and in render method:
<MyComponent {...myAttr} />
Consider you want to pass a custom attribute named myAttr with value myValue, this will work:
<MyComponent data-myAttr={myValue} />
You can use the "is" attribute to disable the React attribute whitelist for an element.
See my anwser here:
Stackoverflow
if you are using es6 this should work:
<input {...{ "customattribute": "somevalue" }} />
I ran into this problem a lot when attempting to use SVG with react.
I ended up using quite a dirty fix, but it's useful to know this option existed. Below I allow the use of the vector-effect attribute on SVG elements.
import SVGDOMPropertyConfig from 'react/lib/SVGDOMPropertyConfig.js';
import DOMProperty from 'react/lib/DOMProperty.js';
SVGDOMPropertyConfig.Properties.vectorEffect = DOMProperty.injection.MUST_USE_ATTRIBUTE;
SVGDOMPropertyConfig.DOMAttributeNames.vectorEffect = 'vector-effect';
As long as this is included/imported before you start using react, it should work.
See attribute value in console on click event
//...
alertMessage (cEvent){
console.log(cEvent.target.getAttribute('customEvent')); /*display attribute value */
}
//...
simple add customAttribute as your wish in render method
render(){
return <div>
//..
<button customAttribute="My Custom Event Message" onClick={this.alertMessage.bind(this) } >Click Me</button>
</div>
}
//...
Depending on what version of React you are using, you may need to use something like this. I know Facebook is thinking about deprecating string refs in the somewhat near future.
var Hello = React.createClass({
componentDidMount: function() {
ReactDOM.findDOMNode(this.test).setAttribute('custom-attribute', 'some value');
},
render: function() {
return <div>
<span ref={(ref) => this.test = ref}>Element with a custom attribute</span>
</div>;
}
});
React.render(<Hello />, document.getElementById('container'));
Facebook's ref documentation
uniqueId is custom attribute.
<a {...{ "uniqueId": `${item.File.UniqueId}` }} href={item.File.ServerRelativeUrl} target='_blank'>{item.File.Name}</a>
Depending on what exactly is preventing you from doing this, there's another option that requires no changes to your current implementation. You should be able to augment React in your project with a .ts or .d.ts file (not sure which) at project root. It would look something like this:
declare module 'react' {
interface HTMLAttributes<T> extends React.DOMAttributes<T> {
'custom-attribute'?: string; // or 'some-value' | 'another-value'
}
}
Another possibility is the following:
declare namespace JSX {
interface IntrinsicElements {
[elemName: string]: any;
}
}
See JSX | Type Checking
You might even have to wrap that in a declare global {. I haven't landed on a final solution yet.
See also: How do I add attributes to existing HTML elements in TypeScript/JSX?
For any custom attributes I use react-any-attr package
https://www.npmjs.com/package/react-any-attr

Categories