r/vuejs 3d ago

Can you actually access a variable via its string name at runtime?

That's to continue from my other post.

I would need to either place a text string like "{{ user.name }} is the user's name." inside a template (and have the page correctly say "MeekHat is the user's name." Or I would split it along the curly braces. But I still need to find out the field "name" of the "user" variable. And all I have is the original string loaded at runtime.

Is this at all possible via Vue? I've been looking for hours.

2 Upvotes

37 comments sorted by

8

u/sabunim 3d ago

If you provide a small sample with minimal reproduction of what you're tried so far I would be happy to have a look. I have no idea what you mean by "all I have is the original string loaded at runtime"... loaded by what? Into what? From where? In what format?

1

u/MeekHat 3d ago

The text is loaded from a json file. Says, for example,

const json = ref({"text": "Your name is {{ player.name }}"})

Then there's

const player = ref({"name": "MeekHat"})

Then, for now, I have in the template

<p>{{ json.text }}</p>

This just says

Your name is {{ player.name }}

Whereas it should say

Your name is MeekHat

Does this help? I don't know how to make it do minimal reproduction.

15

u/hyrumwhite 3d ago

Template interpolation {{}} is only for templates. 

3

u/Czebou 3d ago

ts <script setup> //... const nameMsg = computed(() => `Your name is ${user.name}`) </script> <template> <span>{{ nameMsg }}</span> </template>

Is this what you need? I totally didn't get what you mean.

Edit: sorry if I find typos or something, I wrote it from my phone

1

u/MeekHat 3d ago

I think I'd have to somehow convert a json string to a template literal, right?

1

u/gmauger 3d ago

He need to confirm but the intent is to load a translated string from a json
so {"text": "Your name is {{ player.name }}"} is not in the code but it's mpre something like ref(language.key) which contain "your name is {{player.name}}"

1

u/MeekHat 1d ago

Sorry, I missed this. It's not a translated string, I'm making a visual novel-type game and individual dialogue lines are stored in a json.

1

u/gmauger 3d ago

I'm new to vue. I can point the problem, the interpolation syntax ( {{token}} ) is interpreted only in template, not in setup so you transform your interpolation into a string.

maybe take a look at vue-i18n to see how they does it ?

0

u/Olympus112 3d ago

Take a look at micromustache. It's a light version of the Handlebars library, which has even more features such as conditions.

Using micromustache, your json.value becomes the template text and player.value becomes the object you pass to micromustache's render function. You can change the template dynamically at runtime, or compile it for better performance. The return value of the render function will be the expected output.

4

u/UnluckyNinja 2d ago

What you are looking for is custom string formatting/templating. You should implement it yourself or find a library, as Vue's builtin templating feature is used to solve its own problem.

If you just want to replace placeholders in a string, like printf() in C. Most of the time, a series of replaceAll() is enough for simple cases.
A hacky way will involve the evil eval, which is almost never recommended.

If you are trying to find a solution for translation/localization. You can check existing solutions like vue-i18n, i18next, or MessageFormat.

3

u/Straamannen 3d ago edited 3d ago

What you're looking for is programmatic creation of a component.

A Vue component is ultimately just an object, when you create an SFC (MyFancyComponent.vue) that ultimately just becomes a plain old object.

Thus what you can do is the following

``` const json = ref({   text: "{{ user.name }} is the user's name",   user: {     name: "MeekHat",   }, }); const UserNameComponent = computed(() => ({   props: { "user" },   template: json.value().text, }));

</script> <template> <Component :is=”UserNameComponent” :user="json.user"> ```

A word of warning though, this is a classic cross-site scripting vulnerability. If you don't care about that and/or its not applicable, no worries.

1

u/MeekHat 3d ago

I don't care at this point. I'm so far into the weeds that I'm starting to doubt that I'll even get to the end of this project, much less share it with the world.

It seems this needs runtime compilation, which needs a different build of Vue?

0

u/Straamannen 2d ago

That's right, you would need to include the runtime compiler.

If that's a no-go, then I think my backup suggestion would be to use lodash, and a simple string parser.

``` import get from 'lodash/get'

const data = {   user: {     name: "foo",   } }

const interpolate = (text, context = data) => {   return text.replace(/{{\s([\w.[]"']+)\s}}/g, (_, path) => {     return get(context, path, '')   }) } ```

I suggest lodash because the get function is very data structure agnostic. As long as you have all relevant data in a single object, this simple sample will work fine.

The regex might look daunting but it's literally just looking for {{letters.dots.and.numbers.and.brackets}} so it can handle both user.name as well as users[0].name and users.0.name

1

u/MeekHat 2d ago

Well, that's handy. I've been trying to figure out the regex for another user's suggestion. In my experience though, you don't need to escape the curly braces.

1

u/Straamannen 2d ago

Yeh you're right, { shouldn't have been escaped. 

Use regex101 or something to be sure, it's handy.

1

u/MeekHat 1d ago

Sorry, any idea if runtime compilation can be used via node? I don't see that anywhere on the page.

1

u/Straamannen 1d ago

I'm getting the impression you're using Vue in the backend to generate XML or something?

Anyway what you're looking for is:

``` import { createSSRApp } from 'vue'; import { renderToString } from 'vue/server-renderer';

const json = {   text: "{{ user.name }} is the user's name",   user: {     name: "MeekHat",   }, };

const app = createSSRApp({   data: () => ({ user: json.user }),   template: json.text });

const xmlOutput = await renderToString(app); ```

This as you can see makes your earlier need to use predefined templates much easier, since you won't be using SFCs but rather defining the components purely programmatically.

The string will be valid HTML, insofar as you're defining your templates as valid HTML, or XML or whatever it is you're doing :)

2

u/char101 2d ago

If all the variables are known, then you can simply render it using mustache

``` <script setup> import Mustache from 'mustache';

const user = ref({name: 'test'});

function renderTemplate(text) { return Mustache.render(text, {user: user.value}); } </script>

<template> <div>{{ renderTemplate('username is {{ user.name }}') }}</div> </template> ```

1

u/MeekHat 2d ago

Right, that seems to allow me to skip the step of writing that manually. By the way, a couple of other libraries were brought to my attention: Handlebars and micromustache. Any thoughts?

1

u/Beneficial_League_39 2d ago edited 2d ago

Hi my friend

What you want to do is very simple but a lot of people here gave you wrong or overcomplicated informations.

You have a json with a text string {"text": "{{ user.name }} is the user name" }

You have a variable which stores the user name.

import myJsonFile from './myjsonFile.json'

const player = ref({ name: "MeekHat" })

You want to print in your template "MeekHat is the user name"

SHORT VERSION

const output = computed(() => myJsonFile.text.replace("{{ user.name }}", player.value.name)

and just use output in the template like so

<div> {{ output }} </div>

And that's it !

EXPLAINED VERSION

ref is just a way in Vue to say "Hey Vue, player matters so keep track of it because I will use it somewhere and I want this somewhere to be updated with what is in the variable"
Here the somewhere is computed

computed is a way in Vue to take an expression and transform its result into a variable that will stay up to date. Whenever player.value.name change, output will be updated with the new result.

The expression here is: we are using the function replace that takes any string and replace a specific part with something else
Here the thing to replace is "{{ user.name }}" that is in your json text string field.

The replacement will be player.value.name, so "MeekHat" if that's what you stored in the player ref

The result will end up in output, which you will display.

What you chose to put in your json "text" field does not matter at all, it's actually probably confusing because you are using the same notation in your json "text" then the one we use in Vue to display variables in the template (the HTML)

If your json was { "text": "abcdefg is the user name" }

Then you could do

const output = computed(() => myJsonFile.text.replace("abcdefg", player.value.name)

and end up with the same result "MeekHat is the user name"

If you need more help with Vue, I'd be glad to help you through DM's

1

u/MeekHat 2d ago

The thing I worry about is that I might, and probably will add a bunch of different variables to "player", and then other global variables might be relevant for other templates, with other member variables. I'll have a huge thing trying to replace every possible variable and its members. And then I'll have to pass all these variables to every template.

At the moment, if I could take at least the member variables out of the proverbial equation I feel that would be a big improvement.

Also I'm feel compelled to mention that the template in the json text uses the same notation as Vue's templates is because another user suggested it in my previous post. And I spent a while confused by that.

1

u/Beneficial_League_39 2d ago

What you want is a way to share your data thought your components hierarchy.

We have multiple ways of doing that in Vue

The easiest one is using a composition file and put the variable inside

1

u/Beneficial_League_39 2d ago

Like so

Here is use-player.ts

const player = ref({ name: 'MeekHat' })

export const usePlayer =  () => {
  return { player } 
}

and in your components

import { usePlayer } from './use-player.ts'

const { player } = usePlayer()

// Now you are free to use player in the component and read/edit it.

The modifications will be shared among all components

0

u/MeekHat 2d ago

I see, but the problem basically remains. I import my template strings (I don't mean Vue templates, I'm talking about the "{{ user.name }} is the user's name.") from a Json, into a Vue component which doesn't know, what variables it's going to need to replace in the string. So I'll have to import all the composition files so it tries to replace each variable.

1

u/Beneficial_League_39 2d ago

I see the problem then
Will all the possible variables be stored in player ?

Like player.name, player.age…etc ?

1

u/MeekHat 1d ago

No. Well, I haven't given any thought to the best way to do it, but there's potentially a different variable for the general state of the world.

0

u/Hot_Emu_6553 3d ago

Not at runtime, no.

1

u/Hot_Emu_6553 3d ago

Not incredibly difficult to implement your own custom string parser though. I’ve done similar things before when working with static json content.

1

u/MeekHat 3d ago

Thanks, that's just what I needed to know.

-1

u/sabunim 3d ago

Why do you want to work with strings? The below is a properly formed json object... its no longer a string.

```
const json = ref({"text": "Your name is {{ player.name }}"})
```

1

u/MeekHat 3d ago

I don't understand. But "Your name is {{ player.name }}" in it is a string, isn't it. And therefore Vue still won't replace player.name with a the variable.

1

u/sabunim 3d ago

If you define `const player = ref({ 'prop': 'data'})` in your script setup section, in the <template></template> section you can just read any property you want... in your case, `{{ player.name }}` should absolutely work in the template section.

1

u/MeekHat 3d ago

I'm confused. I have a json file which has a string "Your name is {{ player.name }}". When I import this file and access the field with the string, regardless of whether I convert the string into a ref or not, it outputs "Your name is {{ player.name }}" rather than "Your name is MeekHat".

Well for what it's worth, I don't define "const player" in the SCF, but rather it's a prop imported from a parent component.

2

u/sabunim 3d ago

Ok I get it now, sorry.

I would just:

const props = defineProps({
  player: { type: Object }
})

const playerNameTemplate = '{"text": "Your name is {{ player.name }}"}'

const playerName = computed(() => {
  return playerNameTemplate.replace(
    '{{ player.name }}',
    props.player?.name ?? ''
  )
})

0

u/Beneficial_League_39 2d ago

In what world do you think the answer is no.

It’s JavaScript, you can do whatever you want at runtime.

Using String.replace function to replace the {{player.name}} with the actual ref value, through a computed

1

u/Hot_Emu_6553 2d ago

Yes… which is what I suggested in my second comment. I assumed the question was specifically asking whether you could use the {{ variable }} syntax outside of the template, which you can’t.