How do I pass data to the key and link of a link element? - vue.js

I'm making an app using VueJS and Framework7, and I'm having trouble understanding how to apply dynamic route matching to my app.
My app has two pages, main view and info page. On the main page, there is a list of links that all lead to the info page. However, the links are generated from API data, and I wish to do the same on the info page. What I'm trying to do is pass the id parameter from the API data into the link address, so that it's stored in there even while I load the same info page template. Using that id, I'd like to identify which data to print on the info page from the API data.
Here is my link element:
<f7-list-item v-for="lowerBoss in lowerBosses" :key="lowerBoss.id" :data="lowerBoss" class="single-boss subheading white" link="/boss/lowerBoss.id" onclick="console.log(lowerBoss)">
{{ lowerBoss.name }}
</f7-list-item>
So here I am trying to pass the id from the lowerBoss object into the link address and key. I tried to console.log the object as well, but whenever I click on this link, I get an error saying lowerBoss is not defined.
I am aware that I should most likely be using router-link for this, but I had trouble getting that to work - the links would not work wherever they led. Besides that, I had the same issue with them too.

The answer above is right. You have to remmeber, that every property that written as usual <component link="some-link/object.id"></component> will not be parsed, but will be passed as string. So you have to use :link="'/bla/bla/'+object.id".
lowerBoss will be available inside f7 component as "data", because of :data="lowerBoss" this part of your tag.
Check this Vue.js Passing Static or Dynamic Props
To handle events you have to use vue.js directives Event handling

Try as below
<f7-list-item v-for="lowerBoss in lowerBosses" :key="lowerBoss.id" :data="lowerBoss" class="single-boss subheading white" :link="'/boss/'+lowerBoss.id">
{{ lowerBoss.name }}
</f7-list-item>

Related

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.

How to integer an oembed javascript tag inside a VueJs component?

I have an "embed" field used by my users for transform a simple link to a rich content link (link with image, title, description, etc.).
Example:
Note: I use this library to get all link information: oscarotero/Embed
As you can see, when you provide an url (a twitter tweet by example), the "code" parameter contain the HTML and script tags to include on my frontend:
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">I am so hopelessly in love with #ChucklefishLTD ‘s spooky logo pic.twitter.com/2KtjCNOOUl</p>— Tom Slayed 🔪😱 (#TomJamesSlade) October 12, 2020</blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
The problem is with VueJs and the script tag. When my ajax call is OK, I store all embed informations like that:
this.preview = response.embed
And inside my component, I display the html code with the "v-html" VueJs directive
<div v-if="preview['code']" v-html="preview['code']"></div>
The html is displayed :
The script tag is visible inside the code inspector :
But the script is not loaded :
I have the same problem with all oembed url who provide a javascript tag inside the response.
Note: If I try to load the script directly inside the component, VueJs return this error:
VueCompilerError: Tags with side effect ( and ) are ignored in client component templates
A solution on google is to use the "mounted" event, but it's not compatible with my context.
Because the script I have to load is received from an api and can be different each time depending of the link.
it's bit late but here is an option: The api gives you the possibility to not send the script tag, so you can include it on your own in the html before. Add to the url a omit_script=true and it's gone in the result.
Then you can insert the twitter block. Maybe you have to call twttr.widgets.load() to initialize the tweet.
Edit: Since you're using the plugin, this should be working
$embed = new Embed();
$result = $embed->get('https://www.instagram.com/p/B_C0wheCa4V/');
$result->setSettings([
'oembed:query_parameters' => ['omit_script' => true]
]);
$oembed = $info->getOEmbed();

Trouble with routing Vue.js 2 and browser history

Faced such a problem - I send data to the props of the /router-link/ tag, when I click on the link, I go to the article, it gets the data, everything works. Well, if you press the "back" button in the browser and then "forward" there will be no articles, there will be empty fields without data. How can this be avoided?
This is link to the article
<h3 class="database-article__title">
<router-link
:to="{name : 'article',params: {
id: item.id ,
type:item.type ,
name: item.name,
text: item.text,
author: item.author,
isFavorite: item.isFavorite
}}"> {{item.name}} </router-link>
</h3>
Little part of article-template.vue
<div class="content-type marketing">
{{$route.params.type}}
</div>
<h3 class="database-article__title">
{{$route.params.name}}
</h3>
<div class="database-article__text">
{{$route.params.text}}
</div>
Once again, the data transfer is good, when you click on the link, everything is displayed. The problem is that when clicking on the buttons in the browser "back" and "forward" - the browser history is not saved.
Does anyone know the solution to the problem, or where i can read how to solve it?
Thanks!
My guess is that your article route does not specify any of those params in its path. When you click the link, vue-router will remember the params object you specified in the <router-link> and will be accessible through $route.params in the article component.
However, when you click the browser back then forward buttons, the transition to the article route did not occur by clicking the <router-link> like it did the first time, and since those params were not included in the route's path, $route.params will be empty.
I'm guessing you're just trying to pass data from one route to another. If you want it to persist across history state changes (i.e. browser back/forward), then either:
The data needs to be included in the URL, either as params (e.g. /article/:id/:type etc, this needs to be specified upfront in the route's path) or in the query string (e.g. /article?id=1&type=foo). This isn't ideal for this situation.
(Recommended) Store the item object in such a way that it can be accessed by any route. Vuex is one way, but this may be overkill.
Realistically your URLs should only need to have the article's ID in it, like this /article/1. All the other stuff like type/name/etc don't belong in the URL. From the ID you should be able to fetch the full article object either from a REST API (XHR request), or obtain it from some in-memory data store abstraction (Vuex or anything else really).

Initialize dynamic Component in Code using Vue.js

I am currently developing a web application that is used to display elements for events on a map provided by HERE Maps. I am using Vue.
I have some components, but the relevant component is the component HereMaps.vue which initializes the map using the HERE Maps Api.
The HERE Maps Api provides the possibility to place so called InfoBubbles on the map showing additional information. These InfoBubbles can be provided some HTML-code in order to customize their appearance.
Please refer to the documentation for additional information
Following the documentation the code looks something like this:
let bubble = new H.ui.InfoBubble(marker.getPosition(), {
content: "<div class='someClass'>Some Content</div>"
});
this.ui.addBubble(bubble)
This is happening after mount in the "mounted" method from Vue in the "HereMaps" component.
The Bubbles are added in a "closed" (hidden) form and dynamically "opened" to reveal their content when the corresponding marker icon on the map is clicked. Therefore the HTML-code is present on the DOM after the component is mounted and is not removed at a later stage.
Now instead of supplying custom code within each bubble added to the UI i want to just add a component like this:
let bubble = new H.ui.InfoBubble(marker.getPosition(), {
content: "<myDynamicComponent></myDynamicComponent>"
});
this.ui.addBubble(bubble)
It does not matter to me wether the component is initialized using props or if it is conditionally rendered depending on the state of a global variable. I just want to be able to use the "myDynamicComponent" in order to customize the appearance in a different file. Otherwise the design process gets very messy.
As far as i know this is not possible or at least i was not able to get it work. This is probably due to the fact that the "myDynamicComponent" is not used within the "template" of the "HereMaps" component und thus Vue does not know that it needs to render something here after the directive is added to the DOM in the "mounted" method.
This is what the InfoBubble looks using normal HTML as an argument:
This is what the InfoBubble looks using the component as an argument:
It appears to just be empty. No content of the "myDynamicComponent" is shown.
Does anyone have any idea how i could solve this problem.
Thank You.
Answer is a bit complicated and I bet you wouldn't like it:)
content param can accept String or Node value. So you can make new Vue with rendered your component and pass root element as content param.
BTW, Vue does not work as you think, <myDynamicComponent></myDynamicComponent> bindings, etc exists in HTML only in compile time. After that all custom elements(components) are compiled to render functions. So you can't use your components in that way.
Give us fiddle with your problem, so we can provide working example:)

Compile string with custom elements

I have an Aurelia application in which I'm trying to build a CMS component. This component will load data from the server and this data mainly contains slug, title and content fields.
I also have several global components defined in my application, and I want to be able to use those components in the server so when I pull that data my CMS component is able to transform/compile those custom elements.
An example would be a tab component. I have the tab component with this structure defined:
<tab-panel>
<tab title="First"></tab>
<tab title="Second"></tab>
</tab-panel>
The CMS component will contain a content property which I use to pass a string like this: '<tab-panel><tab title="First"></tab><tab title="Second"></tab></tab-panel>'
The component needs to compile that string and render it in its view. I've checked the enhance API, but it doesn't worked, at least for me. Any other suggestion to dynamically compile/render custom elements??
Thanks a lot in advance.
I've found the solution. I've used a compose element and InlineViewStrategy and it worked well, the components are shows and binding works as expected.
If your custom elements are registered globally using globalResources you can actually using the TemplatingEngine to dynamically insert content into the DOM and then compile it after-the-fact. This blog post goes into detail in how you can do it.
However, I would use this as a last resort. As is mostly always the case, there are much better ways to do something in Aurelia. Using the <compose> element is a great way to dynamically render content in your Aurelia applications and should always be the first port of call.