Is it possible to reuse Vue component which is nested inside parent component, after that parent component is swapped for another component?
My motivation is that I need to have something like "resource component" which is placed in DOM without any re-rendering and to assign it alternately to "consumer components" that make use of its capabilities. To be more specific, in my use case there is one component with <video> element, that is capable to play different sources. And then I have different components for each source, for example: video file, stream, webcam... The main restrictions come from the target platform, which can handle only a limited number of <video> elements and produces unexpected bugs when removing / adding / moving <video> elements in DOM.
Basically, it is very similar to this example from the documentation. But I need to reuse <input> element with this markup:
<template v-if="loginType === 'username'">
<label>Username</label>
<foo>
<input placeholder="Enter your username">
</foo>
</template>
<template v-else>
<label>Email</label>
<bar>
<input placeholder="Enter your email address">
</bar>
</template>
Both parent components (<foo> and <bar>) shouldn't render anything to DOM, while <input> should remain in DOM without any moves or re-rendering or changes in HTMLElement state.
So far I have tried these implementations, but none of them fully satisfies my requirements:
I have tried using key, but it seems to work only for the components with the same name. <foo key="baz"> and <bar key="baz"> won't reuse nested <input>.
I have tried using key on different the components with the same name, but the result remained the same.
I have tried adding undocumented config abstract: true for the <foo> and <bar> components (same as the <template> in the example), but again no reuse of <input>, probably because of the different component names.
I have tried using keep-alive component, but it seems to reuse virtual DOM rather than real DOM, which I need.
I have managed to reuse the <input> component with config functional: true for the <foo> and <bar> components. But this makes the components stateless (no reactive data) and instanceless (no this context), so it's impossible for them to effectively communicate with the <input> component.
I have tried to replace both <foo> and <bar> components with another component <baz>, to make reuse with key working. But I failed to implement some sort of delegation of implementation from <baz> to <foo> and <bar> based on the current requirements.
Related
It seems to me like the x-on directive does not work without the presence of an x-data directive on a parent element.
For example, the below does not work
<button
x-on:click='console.log("hello")'>
left
</button>
It needs to be converted as following for it to work
<div x-data="{}">
<button
x-on:click='console.log("hello")'>
left
</button>
</div>
If I am understanding it correctly and an x-data directive is necessary, why would it be that way?
The x-data directive is required because of the main philosophy of Alpine.js framework.
With a full SPA (single page application) framework, like Vue.js, the main index.html file usually has only a single empty element inside <body>, like <div id="app"></div>. Then we mount our Vue.js application to the #main element and Vue.js takes over the entire frontend. After that by default everything is assumed to be a Vue.js component, like <MyComponent :some-data="42"></MyComponent>, so we don't have to mark them in any other way.
However, Alpine.js follows a different path. It is not intended to be a full SPA framework, like Vue.js. Its main purpose is to "enhance" frontend markup code generated by a backend framework like Django/Laravel/etc (the author of Alpine.js, Caleb Porzio, is the author of Laravel Livewire). So here by default a <button> element is assumed to be a normal button, not an Alpine.js component, therefore Alpine.js does not scan it for Alpine.js specific attributes, like x-on. We have to add the x-data attribute to an HTML element like <button> to mark it as an Alpine.js component. After that Alpine.js scans for directives as such, and takes over the handling of the marked element. This method works well with backend frameworks, where we usually only want to make some elements interactive on the frontend with Alpine.js, but any other logic is handled by the backend.
Nevertheless, if you want you can add an empty x-data attribute to the main <div> element in your markup, that makes Alpine.js work as an SPA framework, so it will scan every element for Alpine.js specific attributes. That's totally fine if you want to use Alpine.js this way. But usually we can define smaller, reusable components, so we put the x-data={} attribute to the specific element or to closest ancestor of the element.
I have a function that takes dom elements and operate on them. It is called pageTransition, and it takes two div elements, and performs a transition animation from the one to the other.
function pageTransition(div1, div2){//do the transition}
for example i can call this function like,
pageTransition (document.body.querySelector("#div1"), document.body.querySelector("#div2"))
That is simple, but let us say I want to pass React class components as my div elements. And that isn't possible because react components are class's not html elements. One quick reminder, this react components in the end will be compiled to div elements with some content during build time. I know I could get around this by doing this
...//the react class
render(
return (
<div id="div1">...</div>//this will allow me to call the above function with the same parameters
)
).
But I was just wondering if there was a magic way to compile this react classes before build time, so rather than giving the id's to the returned div's I was wondering if I could do something like this pageTransition(compile(reactClass), compile(reactClass));
The solution will depend on your intended purpose for pageTransition.
However, there are three potential options you may want to look into:
Statically render a React component into html markup or a string: https://reactjs.org/docs/react-dom-server.html#rendertostaticmarkup
Render two div elements in html and use a React portal to manage what is being rendered in the those divs. This could potentially replace what you are doing in pageTransition. https://reactjs.org/docs/portals.html
Use a ref to access the DOM element that is built from the React component: https://reactjs.org/docs/refs-and-the-dom.html
If you explain what pageTransition does it might help me find a solution for you.
My issue here is that I need to render separate route components to elements created by the backend. It's irregular I'm sure. Essentially I'm starting with an html document and need to render route components to particular elements in the dom.
Example:
If I have four components that each need to be rendered to a pre-generated element.
<body>
<div id="elone" />
<div id="eltwo" />
<div id="elthree" />
<div id="elfour" />
</body>
Now I need to render my respective components to each of those elements. The issue is that if I call ReactDOM.render within the component it doesn't recognize the router, and it doesn't appear that route has anyway to render to a particular element.
Note: I cannot unfortunately write the document within the JS, it has to be pre-generated. I don't need this to be done through react router if there are other solutions, but the components must recognize the router.
I hate answering my own questions, it makes it seem like no one is really answering questions here.
Anyhow the solution is to use ReactDOM.createPortal instead of ReactDOM.render within the components. Make sure to render the component with the router to an element outside of the container that you want to portal your subcomponents to otherwise you'll obviously clobber the elements that your portals are pointing to.
I am using VueJS 2 to build a drag-and-drop layout builder. One of the requirements of that project is to be able to have some components that will allow for custom content to live inside (they will be just a wrapper around that content). And to be really concrete, I am trying to pass in and render another drag-and-drop zone which is implemented in a draggable component.
Basically, I want to pass a VueJS template to the component via a prop and have that template rendered inside of the component. This is necessary because I do not want the UI to limit the needs of the developer and therefore need this to be really extensible.
In the following trivial example I would like the "ui-element" to render the content prop inside of it and use the other prop as a data input.
<ui-element
:content="<draggable :name="contentData"></draggable>"
contentData="col1"
>
</ui-element>
Since just outputting the template will escape it, and v-html directive will treat it as regular HTML and not a template I am lost, not really sure how to get this done.
I spent about an hour or more googling but no luck. Which leaves me to three options:
1) I'm the first one to need this complex use case (unlikely)
2) Doing this is stupid on so many levels that no-one even bothered (if so, please let me know how to get this result in a smarter way)
3) There is a special uber-cool JS term for this which I simply do not know and that made my search attempts futile
You'd want to use slots instead.
In your ui-element component, define a slot like so:
<template>
<div>
<slot name="content"></slot>
</div>
</template>
Then you could pass in the draggable component like so:
<ui-element contentData="col1">
<draggable :name="contentData" slot="content"></draggable>
</ui-element>
Here's a very basic fiddle example of a slot.
I'm using Vue.js to build ui for my html5 game. I have a case where I want to define ui-containers which essentially just group other ui-components and position them to somewhere on screen. So I could have something like this going on:
<ui-container>
<ui-component1></ui-component1>
<ui-component2></ui-component2>
<ui-component3></ui-component3>
</ui-container>
Where I need to add and remove those components dynamically based on data-model which represents the content of the container. The problem is that I'd like to keep ui-container generic, so that I can append any Vue-component to it without having information in template about which components there might be.
I googled and found this example which concerns injecting components dynamically: http://forum.vuejs.org/topic/349/injecting-components-to-the-dom
While the data-driven version in example was easy to make and understand, it uses v-for for tag and therefore requires one to know before hand the type of child-component.
So question is, how I can generalize that example to work with any component dynamically? Should my data-model have the type of component or tag name of it and then interpolate it in v-for? Or is there existing mechanism for this kind of requirement?
You can use special is attribute to dynamically set the type of a component. Here are the docs. The code will look somewhat like:
<div id="app">
<div v-for="component in components" :is="component.type" :value="component.value"></div>
</div>
Working fiddle to play with: Dynamic Vue.js components