Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vue2 Router link from server data

I have a Vue2 SPA page which is loading content from the server. It is editable by client in a CMS.

When user is adding relative link (lets say /about-us), this should be picked by Vue and treated as the menu link (which already has /about-us link).

However link to /about-us added in the content is reloading the whole page, so it is not picked as vue route.

How is it possible to attach router to such links?

What I did so far is changing the content in the backend response.

So I am essentially changing

<a href="/about-us">Text</a>

into

<router-link :to="{ path: '/about-us'}">Text</router-link>

Using:

function parseVueLinks($value)
{
    $pattern = "/<a([^>]*) href=\\\"[^http|https|mailto|tel]([^\\\"]*)\"([^>]*)>(.*?)<(\\/a>)/";
    $replace = "<router-link$1 :to=\"{ path: '$2'}\">$4</router-link>";

    return preg_replace($pattern, $replace, $value);
}

Still no luck.

How is this possible?

like image 704
Trouble Avatar asked Nov 09 '17 10:11

Trouble


3 Answers

If I understand you correctly your issue is not with the link parsing, this looks fine. You want to compile the resulting HTML so Vue router can kick in. There is a function Vue.compile which can help you:

Vue.component('my-component', {
  template: '<div></div>',
  props: {
    html: String
  },
  mounted() {
    let { render, staticRenderFns } = Vue.compile(this.html);
    new Vue({ el: this.$el, render, staticRenderFns, router })
  }
});

With this component you can specify any HTML using the prop html, which will then get compiled in the mounted event and will replace the component template. Please note the router being passed to new Vue(), this is a reference to your Vue router, which is needed so all the <router-link> tags in your HTML can be resolved.

Now you can use this component to compile your HTML like this:

<my-component :html="content"></my-component>

where var content = parseVueLinks('<a href="/about-us">Text</a>').

You can see a working example over here https://codepen.io/anon/pen/BmmjwV

like image 96
Florian Haider Avatar answered Oct 22 '22 07:10

Florian Haider


I think your replacement regex is good but is missing one /. Indeed, after testing it, I see the result of parsing :

<a href="/about-us">Text</a>

outputs :

<router-link :to="{ path: 'about-us'}">Text</router-link>

instead of correct:

<router-link :to="{ path: '/about-us'}">Text</router-link>

(see about-us instead of /about-us)

Could you try this,please:

function parseVueLinks($value)
{
    $pattern = "/<a([^>]*) href=\\\"[^http|https|mailto|tel]([^\\\"]*)\"([^>]*)>(.*?)<(\\/a>)/";
    $replace = "<router-link$1 :to=\"{ path: '/$2'}\">$4</router-link>";

    return preg_replace($pattern, $replace, $value);
}
like image 33
Jona Rodrigues Avatar answered Oct 22 '22 06:10

Jona Rodrigues


The simplest regex pattern to do this is /<a href="([^>]*)">(.+)<\/a>/.

Test example:

console.clear() 

const parseVueLinks = ($value) => {
  const re = /<a href="([^>]*)">(.+)<\/a>/g;
  const matches = re.exec($value);
  return `<router-link :to="{ path: '${matches[1]}'}">${matches[2]}</router-link>`
}

console.log(parseVueLinks('<a href="/about-us">Text</a>'))
console.log(parseVueLinks('<a href="http://google.com">Goooooogle</a>'))

I don't know PHP, but am guessing the equivalent PHP might be (tested at https://www.functions-online.com/preg_match.html):
function parseVueLinks($value)
{
  $pattern = "/<a href="([^>]*)">(.+)<\/a>/";
  $matches = [];
  preg_match($pattern, $replace, $matches);
  return "<router-link :to=\"{ path: '" + $matches[1] + "'}\">" + $matches[2] + "</router-link>"; 
}

I'm wondering about the presence of http|https|mailto|tel in your regex, does this mean you want to do some validation on the link?

If so, using preg_match() allows a second regex step to be performed on $matches[1] before output. It would seem simpler to validate as a second step rather than using one big regex.


Edit following comment

The issue is not in regex. It is in Vue not parsing content pulled from the server

This may not apply if you're using server side rendering, but this is how I apply links from content.

MyComponent.ts

<template>
  <div class="row">
    ...
      <router-link :to="'/' + measure.link" class="measure">
        <i class="measure-icon fa fa-lg" :class="measure.icon" aria-hidden="true">
          <span class="title">{{measure.title}}</span>
        </i>
      </router-link>

Here, measure is an object that is fetched from the server. I you are fetching the full <router-link>, that may work with a Dynamic Component but it seems like overkill, since you know the element will be a <router-link.

Note, if there's also a problem with the server responding with 404 to the click, you can use hash-mode routing (default) by adding # before the link, e.g #/about-us.

Alternatively, set history mode in the Vue router.

const router = new Router({
  routes,
  mode: 'history'
})

This requires the server to redirect to index.html for a 404. See HTML History Mode.

Also, you then need to handle 404 in Vue with a catch-all route,

const routes = [
  ...
  { path: '*', component: NotFoundComponent },
]
like image 28
Richard Matsen Avatar answered Oct 22 '22 07:10

Richard Matsen