Vue.js automatically converting Web Components to elements - vuejs2

I am using Vue.js in an app, but only for parts that require more logic. Otherwise I stick with Web components.
I hit an issue when I am try to mix those. I have a Web Component that only extends an existing element (input in my case). Without Vue.js everything works as expected, but inside Vue, "is" is a Vue attribute (docs) and thus entire element is converted to a name element as specified there.
To give an example, HTML without Vue.js stays as is:
<input type="text" is="test-autocomplete" name="search" />
But with Vue.js, it becomes:
<test-autocomplete type="text" name="search"></test-autocomplete>
Which is wrong. It should stay the same as in first case.
Searching on the web, the only solution I found is to set ignored elements as:
Vue.config.ignoredElements = [
'test-autocomplete',
/^test-/
];
But it does not work and here is where I started running out of ideas. Any help from this point how to make it work is appreciated!
I created a minimum fiddle, where my issue is reproduced. I'd expect both outputs to render identically.
https://jsfiddle.net/q2cre9po/2/

Related

How to setup a netlify form in Nuxt

When I navigate to a form using vue-router by adding a link with a <router-link> element, the form does not work. When I hit submit I get a 404 response.
However, if I navigate to it using an <a> tag (triggering a page reload) then it works perfectly.
I suspect that this has to do with the page rendering as a SPA and for some reason not loading an important part of the form for Netlify unless the form page is reloaded? Why is this happening and is there an elegant solution to the problem? I could just replace all links to forms with tags but I'm sure that there is a better solution, I just don't understand the problem well enough to find it.
For context, I am using Nuxt. The forms are recognized by Netlify on the backend and can accept submission with the tag link so that is not the problem.
Since you're using Nuxt, you probably should go SSG/full static with target: 'static' (hostable on Netlify-like platforms) or with target: 'server' (hostable on Heroku-like platforms) but at any point, you should have ssr: true (default value). When you do have this, the biggest part is done.
In Nuxt, you should use <nuxt-link> rather than <router-link>, it works exactly the same (takes the same params etc) but it's more specific to Nuxt and SSR/SSG compatible while <router-link> is not. More details here: https://nuxtjs.org/docs/2.x/features/nuxt-components#the-nuxtlink-component
So, with all of this it should already work great. If it's not, I will gladly help you spot the issue if you have a github repo.
An alternative solution can be to use some form without any SSR dependency like Formspree: https://formspree.io/ (works fine with any SPA)
It works great, really simple. But I'd rather invite you to make a proper SSR form since you're using Nuxt.
PS: use <a> tags only for external links aka the ones which do not start with your domain name, nothing else. A follow of this kind of link is like a hard refresh and should be avoided at all costs.
EDIT: how to deploy by cleaning the cache.
EDIT on how to achieve a working form:
<template>
<div>
<form
netlify
action="/"
method="POST"
name="Contact"
>
<input type="hidden" name="form-name" value="Contact" />
<!-- ... -->
</form>
</div>
</template>
As told in the docs:
[...] inject a hidden input named form-name [...] and the hidden form-name input’s value matches the name attribute of form
Working fine. Could add a honeypot to it to make it even more secure!

How to get rid of Mismatching childNodes vs. VNodes in NuxtJs [duplicate]

I am using Nuxt.js / Vuejs for my app, and I keep facing this error in different places:
The client-side rendered virtual DOM tree is not matching server-rendered content.
This is likely caused by incorrect HTML markup, for example nesting block-level elements inside <p>, or missing <tbody>.
Bailing hydration and performing full client-side render.
I would like to understand what is the best way to debug this error? Is their a way I can record/get the virtual DOM tree for client and server so I could compare and find where the error lies?
Mine is a large application and manually verifying is difficult.
Partial answer: with Chrome DevTools, you can localize the issue and see exactly what element caused the issue. Do the following (I did that with Nuxt 5.6.0 and Chrome 64.0.3282.186)
Show DevTools in Chrome (F12)
Load the page that causes "the client-side rendered virtual DOM tree..." warning.
Scroll to the warning in DevTools console.
Click at the source location hyperlink of the warning (in my case it was vue.runtime.esm.js:574).
Set a breakpoint there (left-clicking at line number in the source code browser).
Make the same warning to appear again. I'm not saying it is always possible, but in my case I simply reloaded the page. If there are many warnings, you can check the message by moving a mouse over msg variable.
When you found your message and stopped on a breakpoint, look at the call stack. Click one frame down to call to "patch" to open its source. Hover mouse over hydrate function call 4 lines above the execution line in patch. Hyperlink to the source of hydrate would open.
In the hydrate function, move about 15 lines from the start and set a breakpoint where false is returned after assertNodeMatch returned false. Set the breakpoint there and remove all other breakpoints.
Make the same warning to happen again. Now, when breakpoint is hit, execution should stop in the hydrate function. Switch to DevTools console and evaluate elm and then vnode. Here elm seem to be a server-rendered DOM element while vnode is a virtual DOM node. Elm is printed as HTML so you can figure out where the error happened.
For me this error happened cuz get Array list in AsyncData and rendered <tr> tags by v-for, i put v-for codes in <client-only> blocks and problem solved
This error can be really painfull to debug. In order to quickly get the element causing an issue edit node_modules/vue/dist/vue.esm.js and add the following lines :
// Search for this line:
function hydrate (elm, vnode, insertedVnodeQueue, inVPre) {
var i;
var tag = vnode.tag;
var data = vnode.data;
var children = vnode.children;
inVPre = inVPre || (data && data.pre);
vnode.elm = elm;
// Add the following lines:
console.log('elm', elm)
console.log('vnode', vnode)
console.log('inVpre', inVPre)
// ...
You will get in the console the failing node.
There are a lot of ways of fixing this issue, but most of them are not actual fixes, just hacky band-aids. To note a few:
wrap it into <client-only> tags, beware of some important details tho
using a v-show instead of a v-if
trying to hack some lifecycles
etc...
I highly recommend reading this gorgeous article written by Alexander Lichter
https://blog.lichter.io/posts/vue-hydration-error/
He'll explain you that you should diagnose why this happens and fix the actual issue.
Basically each time something is different from what was generated on the server and what is available when done hydrating on the client will cause this error.
Some of which are:
invalid HTML (having a block element inside of a <p>, same goes for an a tag nested into another, etc...)
3rd party scripts messing around with your components
different state on server vs client
any random is risky (new Date() for example)
any page related to authentication
I highly recommend reading the article to understand in Alexandre's own words how to handle this kind of issue. If you're in a hurry you could always use one band-aid fix but try to actually fix the issue for the best performance and to keep the code clean.
I had the same issue as of nuxt version 2.14.0 while implementing vue-particles package. The fix was to surround the tags with no-ssr and it fixed the issue.
EDIT:
Updated variant of the solution (if Nuxt version is above 2.9.0)
<client-only>
<vue-particles>
</vue-particles>
</client-only>
Old solution:
<no-ssr>
<vue-particles>
</vue-particles>
</no-ssr>
Thanks to budden73's answer, I did a little improvement on the debug process.
Open dev tool
click on the warn message, and click on the first line of the warn message, you will be directed to the Sources panel, with a file name vue.runtime.esm.js?xxxx
ctrl+f to search the above file for assertNodeMatch, not the function, but like:
if (process.env.NODE_ENV !== 'production') {
if (!assertNodeMatch(elm, vnode, inVPre)) {
return false
}
}
Add a break point at the line return false
Refresh the page, and the breakpoint will be triggered.
At the right side of the Sources panel, Under Scope->Local, click on the elm element, you will be directed back to the Elements panel.
The above element is the client side rendered element, compare with your code to see the difference.
If you can't find the source of the bug, the brutal way to fix it is using nuxt's <client-only> tag.
Another likely brutal way is described here. Add an isHydrate variable which default is false, set to true in mounted hook, and render the element after the variable set to true.
For Nuxt version above 2.10 it doesn't need to install nothing, just use the default component <client-only> as mentioned https://nuxtjs.org/api/components-client-only/.
Check the previous warning:
In "nuxt": "^2.12.2", You can spot the cause easily from the previous warning.
In my case:
Incorrect
<nuxt-link to="/game42day">
<a>Game For Today</a>
</nuxt-link>
Correct:
<nuxt-link to="/game42day">
Game For Today
</nuxt-link>
If you're rendering a component conditionally with v-if, then you have two options to solve the problem:
The first one is wrapping the element in <no-ssr></no-ssr> tag.
The second approach is replacing v-if with v-show, here is the link to Vue docs.
Turns out, in my case, I had HTML comment tags , which was causing this stupid, annoying error. Took me too long to figure it out but in case it helps someone.
In my case I had to change this:
<v-expansion-panel-header v-text="name" />
to this:
<v-expansion-panel-header>{{ name }}</v-expansion-panel-header>
I also get many errors due to this problem. I list two cases I often encounter, hope can help you.
With vuetify button, when you create a common component, you should use: <v-btn>{{text}}</v-btn>. Example:
<template>
<v-btn
:width="width"
:color="color"
:class="[rounded ? 'rounded-pill' : 'rounded-lg',textColor]"
v-on:click="onClick"
elevation="0"
:outlined="outlined"
:type="type"
:name="name"
:form="form"
:disabled="disabled"
v-bind="$attrs"
>{{ text }}</v-btn>
</template>
Don't use v-html with <p> tag.
Not use: <p v-html='html'></p>.
Use: <div v-html='html'></div>.
Besides, if you use <client-only></client-only>, this problem is definitely solved, but if you need to SEO page or show google ads, it is not good solution.
Ok this is going to sound silly. I tried a bunch of different solutions for about 15 mins such as restarting the server and deleting the .nuxt directory but I was too lazy to use #budden73's big brain solution. What ended up working for me was simply restarting my computer, give it a shot.
What I have found so far from observation is that when you are using third party packages like jQuery (specially), they sometimes inject html tags into the dom. So Vue/Nuxt looses track of the dom tree and starts complaining.
I was having the same problem and after a while I removed all jQuery and replaced jQuery functionality with Vuejs and those error were all gone.
See here for an example of how to deal with integrations (e.g. Google Analytics or FB Pixel) that modify the DOM. Basically create a plugin and exclude from SSR.
https://nuxtjs.org/faq/ga
What about:
extend (config, ctx) {
config.resolve.symlinks = false
}
See this [Vue warn]: The client-side rendered virtual DOM tree is not matching server-rendered content ( Nuxt / Vue / lerna monorepo )
Now that you found the code causing the problem, the first thing you should do is to verify that your markup (possibly coming from an API) is valid. Code like <p><p>Text</p></p> is not valid because a p element doesn’t allow other block elements (like a paragraph tag) inside.
Be aware, that tags are not allowed to have block level elements like <div> or <p> as children. These <span> tags are used default tag for Vue’s transitions though. You can change that though via <Transition tag="div">.
Check if have used any block-level element inside the inline element.
for example: inside , inside
If you have used an HTML table make sure you have used the tag
In my case, I changed my codes from
<p v-html="$md.render(post.content)"></p>
to
<p>{{ $md.render(post.content) }}</p>
In my case this problem was caused by markdownit module, I solved it by changing the html markup used with v-html. I was with <p> at the beginning and I ended with <div>.
I have some <p> in my v-html render (with $md.render()) so take care if you have same problems with different markups.

Vue with Web Component "is" Attribute

I am trying to use a web component which extends an existing element using the "is" attribute tag, but vue takes that attribute and converts it into a custom element.
I need vue to still recognize the v-model and business logic, but I also need vue to not change it to a custom component.
Vue turns this:
<textarea v-model="text" is="my-component"></textarea>
into:
<my-component></my-component>
I have tried setting "v-pre" but that keeps vue from tracking the v-model. I have also tried to set ignoredElements, but vue still convert to a custom element. I looked https://github.com/vuejs/vue/issues/2642 and it seems like this is supposed to be a fixed issue, but maybe I am missing something.
Here is a minimal example of what is happening: https://jsfiddle.net/ntkg1xeq/1/
Notice that the textarea turns into real-set in this example.
I would expect vue to disregard the "is" attribute if it does not match any registered components, and to not rewrite the element thus preventing the web component from working.
Is there any way to use vue and web components like this together?
According to the last comment of Web component with "is" attribute not rendered in the view., <textarea v-pre v-model="text" is="my-component"></textarea> would be
render to <textarea is="my-component"> just as your expectation. However, it seems the issue just fixed Vue 1.x ( Vue 1.0.12 example code ), not Vue 2.x ( your example code ).
So. if you want to set is attribute in textarea and make v-model works in Vue 2.x, I thought $refs is another solution ( Vue 2.x example code by using $refs ).

Target and manipulate single DOM element in vue

Somehow I still can't wrap my head around some core vue concepts.
I have made some simple webpage using phalcon. Created it so, that it would work without JS and now is the time to add some bells and whistles - ajax queries and the like, for the user experience to be better.
I wanted to do everything using vue, to see how it all adds up. But after hours of googling I still can't find solution for the simplest of tasks.
Say: I want to get a text paragraph in a series of <li>-s and change it somewhat. Maybe make excerpt of it and add 'see more' button behind it. Now, in jQuery I would just iterate with each() and perform the tasks. With vue targeting set of DOM elements is much harder for me, probably because of whole paradigm being "the other way round".
I know I could iterate with v-for, but these elements are already in the DOM, taken from the database and templated with volt. I had even this wild idea of creating .js files from phalcon, but it would completely negate my strategy of making functional webpage first and then enhance it progressively.
Frankly speaking I feel like I'm overcomplicating for the sake of it, right now. Is vue even fit for a project like this, or is it exclusively a tool to build app from the ground up?
Vue's templating is client-side, which means if you are delivering an already templated html page (by your backend) there is little vue can still do for you. Vue needs data, not DOM elements to build its viewmodels.
This becomes pretty obvious when building a single page application for example, which would be rendered only on the client-side. You'd simply load the data asynchronously from a backend api (REST for example) and then do all the rendering on the client.
As far as I understand your usecase you want to mix client and server side rendering, rendering most of the non-interactable content using your backend's templating engine and adding some interactivity using vue. In this case you'll need to add some vue components (with their own rendering logic) to your backend template and pass data to that component using vue's data-binding.
Here's an example:
...
<div id="app">
<my-vue-list :products="{% products %}"></my-vue-list>
</div>
...
And in your JS:
var app = new Vue({
el: '#app',
data: {
components: {MyVueList} // You will have to register all the components you want to use here
}
})
Vue provides the ref attribute for registering a reference to a dom element or child component:
// accessible via this.$refs.foo
<li ref="foo">...</li>
Do note, however, that refs are not reactive, as stated in the docs:
$refs is also non-reactive, therefore you should not attempt to use it in templates for data-binding.

Rendering an aurelia custom element in ag-grid header

Is there an "easy" way to render a custom element in ag-grid header cell? The headercomponent interface seems like an overly cumbersome approach to a seemingly simple problem and I have not been successful with this approach. The closest I have come is to use something like:
header-cell-render.bind="myHeaderRenderer"
which is currently a function returning a string of HTML. While this "works" (though I understand it is deprecated), in the sense that the html is injected into the DOM, only primitive HTML renders. Meaning I can return something like:
<input type="checkbox" />
and it will render a checkbox, but I cannot return something like:
<my-custom-element></my-custom-element>
I can see that markup in the DOM, but the element doesn't "process", that is the Aurelia aspect of the control is not executed.
I am using the latest versions of ag-grid, ag-grid-aurelia.
Any help would be greatly appreciated.
This is currently not possible out of the box with ag-grid-aurelia.
This is because ag-grid-aurelia makes no special handling for this binding. The proper way to handle this is using replaceable parts which allows you to specify a template for Aurelia to inject and consume. ag-grid makes this a bit harder since it seems the only native tool for injecting content into the header is by passing an HTML string or an HTML element in the column configuration. This can likely be added to the ag-grid-aurelia library by using a combination of replaceable parts and the #children decorator to gain access to the aurelia-rendered header element and pass it to the column configuration.