How to pass a variable from twig to vueJS (symfony 2.8 and VueJS 2) - javascript

I have a symfony 2.8 application and I recently integrated VueJs 2 as my front-end framework, because it gives a lot of flexibility.
My application is not single page and I use the symfony controllers to render views. All the views are wrapped in a base twig layout:
<!DOCTYPE html>
<html lang="{{ app.request.locale|split('_')[0] }}">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
</head>
<body>
<div id="app">
{% block body %} {% endblock %}
</div>
<script src="{{ asset('bundles/fosjsrouting/js/router.js') }}"></script>
<script src="/js/fos_js_routes.js"></script>
<script type="text/javascript" src="{{ asset('build/vendor-bundle.js') }}"></script>
<script type="text/javascript" src="{{ asset('build/vue-bundle.js') }}"></script>
</body>
</html>
I load most of the JS with webpack, all my vue components and JS dependencies are compiled in vendor-bundle.js and vue-bundle.js. My VueJs instance looks like this:
import './components-dir/component.vue'
import './components-dir/component2.vue'
Vue.component('component', Component);
Vue.component('component2', Component2);
window.onload = function () {
new Vue({
el: '#app',
components: {}
});
};
I want to pass some php variables from the controller to the vuejs componets, but I can't manage to make it work.
A very simple example of a controller looks like this:
/**
* #Route("/contract", name="contract")
* #Method("GET")
*/
public function indexAction()
{
$paymentMethods = PaymentMethod::getChoices();
return $this->render('contracts/index.html.twig', [
'paymentMethods' => $serializer->normalize($paymentMethods, 'json'),
]);
}
All the html, css and js are handled by vueJs. The twig view looks like this:
{% extends 'vue-base.html.twig' %}
{% block body %}
<contracts :paymentMethods="{{paymentMethods | raw}}"></contracts>
{% endblock %}
The contracts.vue component looks like this:
<template>
<div>
<p>Hi from component</p>
</div>
</template>
<script>
export default {
data() {
return {}
},
props: ['paymentMethods'],
mounted: function () {
console.log(this.paymentMethods)
}
}
</script>
<style>
</style>
How can I pass the php variables as props to vueJs ?
In the example above, I don't get any errors, but the property is not passed to vuejs. The console log prints undefined.
I want to be able to do this, because I don't want to have a SPA, but I also want to pass some variables from symfony to vue, because I won't have to make additional requests.

Instead of passing Twig variable as value of Vue attr:
<contracts :payment-methods="{{ twigVar}}"></contracts>
you can render whole using twig:
<contracts {{ ':payment-methods="' ~ twigVar ~ '"' }}></contracts>
Thanks to this you will avoid delimeters conflict between vue and twig.
Also as the value comes directly from twig, it probably wont change upon a time, as it is generated in backend - not in some vue-source - so you don't need to bind it, just pass it like:
<contracts payment-methods="{{ twigVar}}"></contracts>

The simplest way to pass variables from twig to Vue application is:
Twig:
<div id="app" data-foo="{{ foo }}" data-bar="{{ bar }}"></div>
JS:
import Vue from 'vue'
new Vue({
el: '#app',
data: {
foo: '',
bar: ''
},
template: '<div>foo = {{ foo }}</div><div>bar = {{ bar }}</div>',
beforeMount: function() {
this.foo = this.$el.attributes['data-foo'].value
this.bar = this.$el.attributes['data-bar'].value
}
})
If you would like to use a Vue component you can do it the following way:
Twig:
<div id="app" data-foo="{{ foo }}" data-bar="{{ bar }}"></div>
JS:
import Vue from 'vue'
import App from 'App'
new Vue({
el: '#app',
render(h) {
return h(App, {
props: {
foo: this.$el.attributes['data-foo'].value,
bar: this.$el.attributes['data-bar'].value,
}
})
}
})
App.vue:
<template>
<div>foo = {{ foo }}</div>
<div>bar = {{ bar }}</div>
</template>
<script>
export default {
props: ['foo', 'bar'],
}
</script>
Please note if you would like to pass arrays you should convert them to json format before:
Twig:
<div id="app" data-foo="{{ foo|json_encode }}"></div>
and then you should decode json:
JS:
this.foo = JSON.parse(this.$el.attributes['data-foo'].value)

you need to add the following to your .vue file props: ['paymentMethods'] please refer to the following url for complete documentation https://v2.vuejs.org/v2/guide/components.html#Passing-Data-with-Props

Probably late to the party, but if anyone is having the same issue, the problem here was the casing.
CamelCased props like paymentMethods are converted to hyphen-case in html, and can be used like this:
<contracts :payment-methods="{{ paymentMethods | raw }}"></contracts>

Related

Register Vue.js component dynamically from a string

I have a CRUD that enables me to write Vue.js component's code in the textarea like:
<template>
<div><p class='name-wrapper'>{{ model.name }}</p></div>
</template>
<script>
module.exports = {
name: 'NameWrapper',
props: ['model']
}
</script>
<style lang='sass'>
.name-wrapper
color: red
</style>
Then in other component, I fetch this data and want to register it as a dynamic/async, custom component like:
<template>
<component :is='dynamicName' :model='{name: "Alex"}'></component>
</template>
<script>
import httpVueLoader from 'http-vue-loader'
import Vue from 'vue'
export default {
name: 'DynamicComponent',
props: ['dynamicName', 'componentDefinitionFromTextareaAsString'],
beforeCreate: {
// I know that as a second parameter it should be an url to the file, but I can't provide it, but I would like to pass the contents of the file instead there:
httpVueLoader.register(Vue, this.$options.propsData.componentDefinitionFromTextareaAsString)
// I was trying also:
Vue.component(this.$options.propsData.dynamicName, this.$options.propsData.componentDefinitionFromTextareaAsString)
}
}
</script>
As far as I know, httpVueLoader needs the url to the .vue file instead - is there a way to pass there the code itself of the component?
I am aware that passing and evaluating <script></script> tag contents can cause security issues, but I really need to do it that way.
I've read also about Vue.js compile function, but that works only for templates, not the code of the component (so the script tags again).
Is it even possible to achieve such functionality in Vue.js?
It should be possible to use a data: URI with http-vue-loader, like this:
const vueText = `
<template>
<div class="hello">Hello {{who}}</div>
</template>
<script>
module.exports = {
data: function() {
return {
who: 'world'
}
}
}
<\/script>
<style>
.hello {
background-color: #ffe;
}
</style>
`
const MyComponent = httpVueLoader('data:text/plain,' + encodeURIComponent(vueText))
new Vue({
el: '#app',
components: {
MyComponent
}
})
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<script src="https://unpkg.com/http-vue-loader#1.4.1/src/httpVueLoader.js"></script>
<div id="app">
<my-component></my-component>
</div>
If that doesn't work for some reason (maybe because one of your target browsers doesn't support it) then you could get it working by patching httpRequest. See https://www.npmjs.com/package/http-vue-loader#httpvueloaderhttprequest-url-. The documentation focuses on patching httpRequest to use axios but you could patch it to just resolve the promise to the relevant text.

Vue.js 2 component not displaying data

I'm trying to make a simple vue component work:
<div id="app">
<my-component></my-component>
</div>
<script>
Vue.component('my-component', {
template: '<div>A custom component! {{ xstr }} aa</div>',
data: function() {
return {
xstr: 'i really want this to be visible',
};
}
});
window.app = new Vue({
el: '#app',
});
</script>
But apparently xstr is not displayed. What am I missing?
I identified my issue, but it's not related to vue. I used jinja2 for generating the html, and since jinja uses the {{ }} syntax for templating as well, the two systems interfered.

How to pass JavaScript variables to React component

I'm somewhat new to React and I'm having some trouble passing some variables from my Django server to my React components. Here's what I have:
The server is Django, and I have a url mydomain.com/testview/ that gets mapped to a views.py function testview:
def testview(request):
now = datetime.datetime.now()
return render(request, 'testview.html', {
'foo': '%s' % str(now),
'myVar': 'someString'
})
In other words, running testview will render the template file testview.html and will use the variables foo and myVar.
The file testview.html inherits from base.html which looks like this:
<!doctype html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
</head>
<body>
{% block main %}{% endblock %}
</body>
</html>
The file test.html basically inserts the needed code into block main:
testview.html:
{% extends "base.html" %}
{% load render_bundle from webpack_loader %}
{% block main %}
<script type="text/javascript">
var foo = {{ foo }};
var myVar = {{ myVar }};
</script>
<div id="App"></div>
{% render_bundle 'vendors' %}
{% render_bundle 'App' %}
{% endblock %}
Note that just before the div id="App", I created a couple of javascript variables foo and myVar and set them to the values from Django.
Now to REACT: my file App.jsx looks like this:
import React from "react"
import { render } from "react-dom"
import AppContainer from "./containers/AppContainer"
class App extends React.Component {
render() {
return (
<AppContainer foo={props.foo} myVar={props.myVar}/>
)
}
}
render(<App foo={window.foo} myVar={window.myVar}/>, document.getElementById('App'))
In other words, my App.jsx file renders the App component, passing in foo and myVar. Inside class App, I assumed these were props so I pass these to AppContainer using props.foo and props.myVar. My class AppContainer is inside a components folder and looks like this:
import React from "react"
import Headline from "../components/Headline"
export default class AppContainer extends React.Component {
render() {
return (
<div className="container">
<div className="row">
<div className="col-sm-12">
<Headline>Running App! foo is {props.foo}, Here is a string: {props.myVar}</Headline>
</div>
</div>
</div>
)
}
}
However, none of this seems to work. I just get a blank page. What am I doing wrong?
if foo and myVar is string you should declare
var foo = "{{ foo }}";
var myVar = "{{ myVar }}";
So this is what I needed to do to get it to work. First, I used Giang Le's answer above and in my testview.html file (a Django template file), I put quotes around the variables as they were indeed strings. Next, I changed the render statement in my App.jsx file to be this:
render(<App foo={foo} myVar={myVar}/>, document.getElementById('App'))
This used Bruno Vodola Martins' answer to access foo and myVar as javascript globals. I also had to use this.props.foo instead of props.foo in my App class:
class App extends React.Component {
render() {
return (
<AppContainer foo={this.props.foo} myVar={this.props.myVar}/>
)
}
}
And I did the same thing in containers/AppContainer.jsx:
export default class AppContainer extends React.Component {
render() {
return (
<div className="container">
<div className="row">
<div className="col-sm-12">
<Headline>App! foo is {this.props.foo}, Here is a string: {this.props.myVar}</Headline>
</div>
</div>
</div>
)
}
}
Bottom line: put quotes around string variables from Django, and use this.props.foo instead of just props.foo.

Locutus in VueJS template engine

How can i implement Locutus in my VueJS application? I am using webpack.
I tried to to the following in my single file component:
<template>
<div>{{ nl2br('My string here') }}</div>
</template>
<script>
var nl2br = require('locutus/php/strings/nl2br');
export default {
// VueJS
}
</script>
It says that my template:
can't render because $vm.nl2br, doesn't exists.
You can not access methods from other libraries in the view, You can use those in any of vue methods and invoke that method from vue template, so you cab have something like following:
<template>
<div>{{ getFromNl2br('My string here') }}</div>
</template>
<script>
var nl2br = require('locutus/php/strings/nl2br');
export default {
methods: {
getFromNl2br: function(str) {
return nl2br(str)
}
}
}
</script>

How can I bind the html <title> content in vuejs?

I'm trying a demo on vuejs. Now I want the html title to bind a vm field.
The below is what I tried:
index.html
<!DOCTYPE html>
<html id="html">
<head>
<title>{{ hello }}</title>
<script src="lib/requirejs/require.min.js" data-main="app"></script>
</head>
<body>
{{ hello }}
<input v-model="hello" title="hello" />
</body>
</html>
app.js
define([
'jquery', 'vue'
], function ($, Vue) {
var vm = new Vue({
el: 'html',
data: {
hello: 'Hello world'
}
});
});
But the title seemed not bounded, how to make it work?
There are essentially two ways to solve it.
Use an existing Package
For example, vue-meta:
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App',
metaInfo: {
// if no subcomponents specify a metaInfo.title, this title will be used
title: 'Default Title',
// all titles will be injected into this template
titleTemplate: '%s | My Awesome Webapp'
}
}
</script>
Create your own Component
Create a vue file containing:
<script>
export default {
name: 'vue-title',
props: ['title'],
watch: {
title: {
immediate: true,
handler() {
document.title = this.title;
}
}
},
render () {
},
}
</script>
Register the component using
import titleComponent from './title.component.vue';
Vue.component('vue-title', titleComponent);
Then you can use it in your templates, e.g.
<vue-title title="Static Title"></vue-title>
<vue-title :title="dynamic.something + ' - Static'"></vue-title>
You can do it with 1 line in the App.vue file, like this:
<script>
export default {
name: 'app',
created () {
document.title = "Look Ma!";
}
}
</script>
Or change the <title> tag content in public/index.html
<!DOCTYPE html>
<html>
<head>
<title>Look Ma!</title> <!- ------ Here ->
</head>
...
This answer is for vue 1.x
using requirejs.
define([
'https://cdn.jsdelivr.net/vue/latest/vue.js'
], function(Vue) {
var vm = new Vue({
el: 'html',
data: {
hello: 'Hello world'
}
});
});
<!DOCTYPE html>
<html id="html">
<head>
<title>{{ hello }}</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.2.0/require.js" data-main="app"></script>
</head>
<body>
{{ hello }}
<input v-model="hello" title="hello" />
</body>
</html>
you can do it like this using the ready function to set the initial value and watch to update when the data changes.
<html>
<head>
<title>Replace Me</title>
</head>
<body>
<script src="https://cdn.jsdelivr.net/vue/latest/vue.js"></script>
<div id="app">
<input v-model="title">
</div>
<script>
new Vue({
el: '#app',
ready: function () {
document.title = this.title
},
data: {
title: 'My Title'
},
watch: {
title: function (val, old) {
document.title = val
}
}
})
</script>
</body>
</html>
also i tried this based on your original code and it works
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<script src="https://cdn.jsdelivr.net/vue/latest/vue.js"></script>
<div id="app">
<input v-model="title">
</div>
<script>
new Vue({
el: 'html',
data: {
title: 'My Title'
}
})
</script>
</body>
</html>
Just to chime in here. I have read that VueJS wants nothing to do with the meta stuff so I would do such things outside of the "VueJS" realm.
Basically make a plain vanilla js service like below. Here you could add all the functions to handle the meta data stuff such as the Open Graph data.
meta.js
export setTitle(title) {
document.title = title
}
Now we can import the service in main and then provide it to any component in the app who wants it. I could even use my meta service in other projects too which use different frameworks like React or Angular. Portability is super cool!
main.js
import meta from './meta'
new Vue({
router,
render: h => h(App),
provide: {
meta: meta
}
}).$mount('#app')
Here the component injects the meta service it wants to use.
someView.vue
export default {
name: 'someView',
inject: ['meta'],
data: function() {
returns {
title: 'Cool title'
}
},
created: function() {
this.meta.setTitle(this.title);
}
}
This way the meta service is decoupled from the app because different parent components can provide different versions of the meta service. Now you can implement various strategies to see which one is right for you or even different strategies per component.
Basically the inject walks up the component hierarchy and takes the meta service from the first parent who provides it. As long as the meta service follows a proper interface, you're golden.
Decoupling with DI is super cool 😃
Title and meta tags can be edited and updated asynchronously.
You can use state management, create a store for SEO using vuex and update each part accordingly.
Or you can update the element by yourself easily
created: function() {
ajax().then(function(data){
document.title = data.title
document.head.querySelector('meta[name=description]').content = data.description
})
}
If you are using Vuex and want <title> to be part of your application state, then:
create a pageTitle state variable in Vuex
map the state to the template using mapState()
watch it in template, probably add immediate: true to trigger the watcher right away
in watcher, document.title = pageTitle
This will allow you to manage title with Vuex and keep them in sync. I found it useful for SPAs.
By doing this you don't have to mess with your original HTML template, as most of the time Vue root template resides inside <body>.
This is for Vue 2.x.
router.beforeEach((to, from, next) => {
let mohican = to.path; if (mohican == '/') mohican = 'Home'
document.title = mohican.replace('/','');
next();
return;
});
I have an application toolbar component which is common for all pages of my SPA website and is nested in App.vue. In every page I update my common toolbar title in the created hook of the page using Vuex store:
//in every page.vue
created() {
this.$store.commit('toolBar', { pageTitle: this.pageTitle, ... })
},
To automatically update the website title (along with the toolbar title) I use this mutation in the store:
//store.js
toolBar(state,val){
document.title = val.pageTitle
state.toolBar = val
},
Similarly, I use the same mechanism to update e.g. SEO metadata
just pass
:title="data.name"

Categories