Pass Javascript Variable to Vue - javascript

How can i pass a Javascript Variable to a Vue Component?
I have this jQuery function which generates the menu and pushes all the Values inside the array menu:
var menu = [];
$(document).ready(function(){
$('.service-desc-wrap h2,.cta-button').each(function(){
if($(this).hasClass('cta-button')) {
if($(this).attr('title') && $(this).attr('href')) {
var linkTitle = $(this).attr('title');
var href = $(this).attr('href');
data = [
title = linkTitle,
href = href,
]
menu.push(data);
$('#menu, #menuMobile').append('<a class="menuText" href="'+href+'">'+linkTitle+'</a>')
};
} else {
var tag = $.slugify($(this).text());
$(this).attr('id',tag);
var linkTitle = $(this).text();
if($(this).attr('title')) {
var linkTitle = $(this).attr('title');
};
data = [
title = linkTitle,
href = tag,
]
menu.push(data);
$('#menu, #menuMobile').append('<a class="menuText" href="#'+tag+'">'+linkTitle+'</a>')
}
});
});
I want to pass the array to a Vue Component called
<service-menu-component></service-menu-component>
The jQuery Function and the Component are inside a blade.php file, i'm using Laravel as a backend.

Any Vue component has access to the global scope (a.k.a window object), in which $ performs. You don't have to do anything special about it. In simpler words, if a variable has been declared in global scope at the time your Vue component is created - Vue can access it. But Vue won't react to later mutations performed on the contents of that variable. Not out of the box, anyway.
In Vue, that behavior is called reactivity. If that's what you want, you could use Vue.observable():
declare a const, holding a reactive reference (store.menu in this example - name it to whatever makes sense to you)
use a computed in your Vue component, returning the reactive reference
at any point, (before or after Vue instance's creation) modify the reference from anywhere (including outside Vue component/instance) and the Vue instance will get the change
Proof of concept:
Vue.config.productionTip = false;
Vue.config.devtools = false;
// you don't need the above config, it just suppresses some warnings on SO
// declare store, with whatever contents you want:
const store = Vue.observable({menu: []});
// optionally push something to menu:
// works before Vue instance was created
store.menu.push({foo: 'bar'});
$(function() {
// optionally push something to menu
// also works after Vue instance was created - i.e: 3s after $(document).ready()
setTimeout(function() {
store.menu.push({boo: 'far'});
}, 3000)
})
new Vue({
el: '#app',
computed: {
menu() {
// this injects the store's menu (the reactive property) in your component
return store.menu
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.12/vue.js"></script>
<div id="app">
<pre v-text="menu"></pre>
</div>
The computed doesn't have to be on the root Vue element (it can be inside your <service-menu-component> component). The above is just a basic implementation, to demo the principle.

Use props:
<service-menu-component :data=YOUR_ARRAY></service-menu-component>
In the component:
props: ['data'] // the data being passed.

Yes, It's possible you need to import this jQuery snippet file to your parent component, and then pass it down to your service-menu-component.
Here's how the code should look like:
Your Parent.vue
<template>
<service-menu-component :data=menu></service-menu-component>
</template>
<script src="<pathToYourJqueryFile>"></script>
And then in your service-menu-component:
<template>
<div>
{{ menu }}
</div>
</template>
<script>
export default {
name: 'service-menu-component',
props: {
menu: {
type: Array
}
}
}
</script>

What made it work and seemed simple after trying different things is moving the jQuery function inside the component mounted hook like this:
mounted() {
var menu = [];
$(document).ready(function(){
$('.service-desc-wrap h2,.cta-button').each(function(){
if($(this).hasClass('cta-button')) {
if($(this).attr('title') && $(this).attr('href')) {
var linkTitle = $(this).attr('title');
var href = $(this).attr('href');
data = {
'title': linkTitle,
'href': href,
}
menu.push(data);
$('#menu, #menuMobile').append('<a class="menuText ml-2" href="'+href+'">'+linkTitle+'</a>')
};
} else {
var tag = $.slugify($(this).text());
$(this).attr('id',tag);
var linkTitle = $(this).text();
if($(this).attr('title')) {
var linkTitle = $(this).attr('title');
};
var data = {
'title': linkTitle,
'href': tag,
}
menu.push(data);
$('#menu, #menuMobile').append('<a class="menuText ml-2" href="#'+tag+'">'+linkTitle+'</a>')
}
});
console.log(menu);
});
this.menu = menu;
},
It worked like a charm.

#Şivam Kuvar verdiği örnekteki gibi :data='menu' yazan yeri :data="json_encode($controllerdata)" ile değiştir ve verilerini kullanmaya hazırsın. veri yapına göre #{{ data.data[0].title }} olabilir örneğin veya #{{ data.title }} bu gelen veriye bağlı dostum.
Just like #Şivam Kuvar's example, replace :data='menu' with :data="json_encode($controllerdata)" and you're ready to use your data. According to the data structure, it can be #{{ data.data[0].title }} for example or #{{ data.title }} it depends on the incoming data, my friend.

Related

How do I set a Svelte Component's event listener and inner HTML for slot dynamically via JavaScript?

JavaScript:
const dispatchEvent = createEventDispatcher();
let dialog: Dialog;
HTML
<Dialog bind:this={dialog} on:close={() => {dispatchEvent("deletedPost", postIndex)}} modal={true} open={false}>
... Here I want to insert some HTML to be used in the slot of the "Dialog" component.
</Dialog>
My question is, how do I set the on:close and the inner HTML dynamically via JS?
I have this demand because this Dialog component should be reused for multiple purposes by its parent component. Sometimes it displays info about a post has been created, sometimes deleted, sometimes edited and so on.
And each of the cases requires different content in the component as well as a different handler for the close event emitted by the Dialog.
Should I use something like
dialog.$on = handlerFunction;
// I didn't find a possible property for setting HTML though
Thanks!
There currently is no way to programmatically interact with the slots (unless you want to investigate the Svelte internals and rely on implementation details which may change).
Either pass in a full component via the props (which can be rendered using <svelte:component>), insert only text or use {#html} to render HTML content (which is not recommended for security reasons).
(The syntax for the events is dialog.$on('close', event => ...) as detailed in the client-side API section.)
Example for all mentioned property types:
<!-- Dialog.svelte -->
<script>
import { createEventDispatcher } from 'svelte';
export let open = true;
export let component = null;
export let text = null;
export let html = null;
const dispatch = createEventDispatcher();
$: dispatch(open ? 'open' : 'close');
</script>
<div>
<h2>Dialog</h2>
{#if component}
<svelte:component this={component} />
{/if}
{#if text}
{text}
{/if}
{#if html}
{#html html}
{/if}
<div><button on:click={() => open = false}>Close</button></div>
</div>
Usage:
// ...
import Content from './Content.svelte';
const d = new Dialog({
// ...
props: { text: 'Hello world' },
});
// or
const d = new Dialog({
// ...
props: { html: '<input placeholder="Text here"/>' },
});
// or
const d = new Dialog({
// ...
props: { component: Content },
});
d.$on('close', () => d.$destroy());
REPL
As noted, #html is dangerous and ideally should only be used with XSS sanitization in place.

Render Vue component tag added by external code

I'm trying to add simple Vue components to a legacy app. The goal is to be able use some newer technologies without rewriting everything.
Basically I want to be able to put <tags-input> element wherever I want and have Vue replace it with a component.
I have instantiated Vue and added some <tags-input> elements to the html. I use them as parts of table rows generated originally by ASP WebForms.
import Vue from 'vue'
import TagsInput from './ClientTags'
import axios from 'axios'
import VueAxios from 'vue-axios'
Vue.use(VueAxios, axios)
import '../style.css';
Vue.config.productionTip = false
window.VueApp = new Vue({
el: '#app',
components: {
TagsInput
}
})
Initially the <tags-input> elements render nicely. The problem is that external legacy JS code adds new rows to the table (WebForms performs some auto-magic pagination). After the new rows are added to the DOM, Vue doesn't render <tags-input> elements inside them.
So, my goal is:
Whenever a new <tags-input> element is added to the DOM (by external JS), it should be rendered by Vue and added to the window.VueApp.
I finally figured it out (thanks to Sphinx comment). I probably should not be using Vue app at all in my case. Instead I should manually create and mount my components like so:
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script>
var MyComponent = Vue.component('my-component', {
// camelCase in JavaScript
props: ['someTitle'],
template: '<b>[XYZ {{ someTitle }}]</b> '
})
function dashToCamelCase( myStr ) {
return myStr.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); });
}
function getAttributes ( node ) {
var i,
attributeNodes = node.attributes,
length = attributeNodes.length,
attrs = {};
for ( i = 0; i < length; i++ ) attrs[dashToCamelCase(attributeNodes[i].name)] = attributeNodes[i].value;
return attrs;
}
function renderExisting(componentElementName, componentCreator){
$(componentElementName).each(function(){
var props = getAttributes(this)
var component = componentCreator(props)
component.$mount(this)
})
}
function renderNew(appElementId, componentElementName, componentCreator){
var obs = new MutationObserver(function(mutations, observer) {
$.each(mutations, function (i, mutation) {
var addedNodes = $(mutation.addedNodes);
var selector = componentElementName
var filteredEls = addedNodes.find(selector).addBack(selector);
filteredEls.each(function(){
var props = getAttributes(this)
var component = componentCreator(props)
component.$mount(this)
});
});
});
var canvasElement = $(appElementId)[0];
obs.observe(canvasElement, {childList: true, subtree: true});
}
function setUpRendering(appElementId, componentElementName, componentCreator){
renderExisting(componentElementName, componentCreator)
renderNew(appElementId, componentElementName, componentCreator)
}
$(function(){
setUpRendering('#myApp', 'my-component', (props) => new MyComponent({propsData: props}))
});
</script>
<script>
function addMyTag(){
$('#myApp').append( '<my-component some-title="' + (new Date()).getTime() + '"></my-component>' )
}
</script>
</head>
<body>
<button onclick='addMyTag()'>Add!</button>
<div id="myApp">
<my-component some-title="aaa"></my-component>
<my-component some-title="bbb"></my-component>
<my-component some-title="ccc"></my-component>
<div>
</body>
</html>
I will be happy to accept a better answer.

Vue.js mount component after DOM tree mutation to add a vue component

I have a use case (below) where I need to mount (if thats the correct term) a Vue.js component template that was inserted into the DOM via jQuery, I can setup a Mutation Observer or react to certain events that are triggered when the mutation happens.
I am using Vue.js v2
Here is a simple example I put together to illustrate the point:
live jsFiddle https://jsfiddle.net/w7q7b1bh/2/
The HTML below contains inlined-templates for two components
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.13/dist/vue.min.js"></script>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<div id="app">
<!-- The use of inline-template is required for my solution to work -->
<simple-counter inline-template>
<button v-bind:style="style" v-on:click="add">clicks: {{ counter }}</button>
</simple-counter>
<simple-counter inline-template>
<button v-on:click="counter += 1">{{ counter }}</button>
</simple-counter>
</div>
<button id="mutate">Mutate</button>
The js:
// simple counter component
Vue.component('simple-counter', {
data: function() {
return {
counter: 0,
style: {
color: 'red',
width: '200px'
}
}
},
methods: {
add: function() {
this.counter = this.counter + 1;
this.style.color = this.style.color == 'red' ? 'green' : 'red';
}
}
})
// create the Vue instance
var initV = () => new Vue({
el: '#app'
});
// expose the instance for later use
window.v = initV();
// click handler that will add a new `simple-counter` template to the Vue.el scope
$('#mutate').click(function(){
$('#app').append(` <div is="simple-counter" inline-template>
<button v-bind:style="style" v-on:click="add">click to add: <span class="inactive" v-bind:class="{ active: true }">{{ counter }}</span></button></div>`)
// do something after the template is incerted
window.v.$destroy()
window.v = initV(); // does not work
})
As mentioned in the code, destroying the re-instantiating the Vue instance does not work, I understand why, the templates for the components are changed on first Vue instantiation to their final HTML, when you try and instantiate a second time, templates are not there, components are not mounted
I'd like to be able to find the newly added components after mutation and mount only those, is that possible? and how?
UPDATE:
I was able to find a way to do it via instantiating a new Vue instance with el set to the specific mutated part of the DOM as opposed to the whole #app tree:
$('#mutate').click(function(){
var appended =
$(`
<div is="simple-counter" inline-template>
<button v-bind:style="style" v-on:click="add">
click to add: {{ counter }}
</button>
</div>`
).appendTo($('#app'));
var newV = new Vue({el: appended[0]});
});
Seems to work, but also looks ugly and I am not sure what other implications this might have..
Use Case:
I am working on a way to write Vue.js components for a CMS called Adobe Experience Manager (AEM).
I write my components using inlined-template which gives me the advantage of SEO as well as server-side rendering using another templating language called HTL.
The way AEM authoring works is that, when a component is edited (via a dialog), that specific component is re-rendered on the server-side then injected back to the DOM to replace the old component, all done via Ajax and jQuery (no browser refresh).
Here is an example
AEM component template:
<button>${properties.buttonTitle}</button>
Here is what an author might do:
author visits the authoring page
opens the button component dialog to edit
changes the buttonTitle to "new button title"
Saves
upon saving, an ajax is sent, the component HTML is re-rendered on the server and returned is the new HTML. That HTML now replaces the old HTML via jQuery (mutates the DOM)
This is fine for static components, but if this was a Vue.js component, how do I dynamically mount it while keeping other components mounted.
An easy solution to this is to refresh the page... but that is just bad experience... There has to be a better way.
Thanks to #liam I was able to find an appropriate solution to my problem
After mutating the DOM with the HTML template, keep a reference to that template's parent element
for example:
var $template = $('<div is="simple-counter" inline-template> ..rest of template here.. <div>').appendTo('#app') // app is the Vue instance el or a child of it
Now you can create a new instance of your component and add $template to it as the el property
if my component was:
var simpleCounterComponent = Vue.component('simple-counter', {
data: function() {
return {
counter: 0,
style: {
color: 'red',
width: '200px'
}
}
},
methods: {
add: function() {
this.counter = this.counter + 1;
this.style.color = this.style.color == 'red' ? 'green' : 'red';
}
}
})
I can do:
var instance = new simpleCounterComponent({
el: $template.get(0) // getting an HTML element not a jQuery object
});
And this way, that newly added template has become a Vue component
Take a look at this fiddle for working example based on the question:
https://jsfiddle.net/947ojvnw/11/
One way to instantiate Vue components in runtime-generated HTML is:
var ComponentClass = Vue.extend({
template: '...',
});
var instance = new ComponentClass({
propsData: { name: value },
});
instance.$mount('#uid'); // HTML contains <... id="uid">
...
instance.$destroy(); // if HTML containing id="uid" is dropped
More here (I am not affiliated with this site)
https://css-tricks.com/creating-vue-js-component-instances-programmatically/

DOM element to corresponding vue.js component

How can I find the vue.js component corresponding to a DOM element?
If I have
element = document.getElementById(id);
Is there a vue method equivalent to the jQuery
$(element)
Just by this (in your method in "methods"):
element = this.$el;
:)
The proper way to do with would be to use the v-el directive to give it a reference. Then you can do this.$$[reference].
Update for vue 2
In Vue 2 refs are used for both elements and components: http://vuejs.org/guide/migration.html#v-el-and-v-ref-replaced
In Vue.js 2 Inside a Vue Instance or Component:
Use this.$el to get the HTMLElement the instance/component was mounted to
From an HTMLElement:
Use .__vue__ from the HTMLElement
E.g. var vueInstance = document.getElementById('app').__vue__;
Having a VNode in a variable called vnode you can:
use vnode.elm to get the element that VNode was rendered to
use vnode.context to get the VueComponent instance that VNode's component was declared (this usually returns the parent component, but may surprise you when using slots.
use vnode.componentInstance to get the Actual VueComponent instance that VNode is about
Source, literally: vue/flow/vnode.js.
Runnable Demo:
Vue.config.productionTip = false; // disable developer version warning
console.log('-------------------')
Vue.component('my-component', {
template: `<input>`,
mounted: function() {
console.log('[my-component] is mounted at element:', this.$el);
}
});
Vue.directive('customdirective', {
bind: function (el, binding, vnode) {
console.log('[DIRECTIVE] My Element is:', vnode.elm);
console.log('[DIRECTIVE] My componentInstance is:', vnode.componentInstance);
console.log('[DIRECTIVE] My context is:', vnode.context);
// some properties, such as $el, may take an extra tick to be set, thus you need to...
Vue.nextTick(() => console.log('[DIRECTIVE][AFTER TICK] My context is:', vnode.context.$el))
}
})
new Vue({
el: '#app',
mounted: function() {
console.log('[ROOT] This Vue instance is mounted at element:', this.$el);
console.log('[ROOT] From the element to the Vue instance:', document.getElementById('app').__vue__);
console.log('[ROOT] Vue component instance of my-component:', document.querySelector('input').__vue__);
}
})
<script src="https://unpkg.com/vue#2.5.15/dist/vue.min.js"></script>
<h1>Open the browser's console</h1>
<div id="app">
<my-component v-customdirective=""></my-component>
</div>
If you're starting with a DOM element, check for a __vue__ property on that element. Any Vue View Models (components, VMs created by v-repeat usage) will have this property.
You can use the "Inspect Element" feature in your browsers developer console (at least in Firefox and Chrome) to view the DOM properties.
Hope that helps!
this.$el - points to the root element of the component
this.$refs.<ref name> + <div ref="<ref name>" ... - points to nested element
💡 use $el/$refs only after mounted() step of vue lifecycle
<template>
<div>
root element
<div ref="childElement">child element</div>
</div>
</template>
<script>
export default {
mounted() {
let rootElement = this.$el;
let childElement = this.$refs.childElement;
console.log(rootElement);
console.log(childElement);
}
}
</script>
<style scoped>
</style>
So I figured $0.__vue__ doesn't work very well with HOCs (high order components).
// ListItem.vue
<template>
<vm-product-item/>
<template>
From the template above, if you have ListItem component, that has ProductItem as it's root, and you try $0.__vue__ in console the result unexpectedly would be the ListItem instance.
Here I got a solution to select the lowest level component (ProductItem in this case).
Plugin
// DomNodeToComponent.js
export default {
install: (Vue, options) => {
Vue.mixin({
mounted () {
this.$el.__vueComponent__ = this
},
})
},
}
Install
import DomNodeToComponent from'./plugins/DomNodeToComponent/DomNodeToComponent'
Vue.use(DomNodeToComponent)
Use
In browser console click on dom element.
Type $0.__vueComponent__.
Do whatever you want with component. Access data. Do changes. Run exposed methods from e2e.
Bonus feature
If you want more, you can just use $0.__vue__.$parent. Meaning if 3 components share the same dom node, you'll have to write $0.__vue__.$parent.$parent to get the main component. This approach is less laconic, but gives better control.
Since v-ref is no longer a directive, but a special attribute, it can also be dynamically defined. This is especially useful in combination with v-for.
For example:
<ul>
<li v-for="(item, key) in items" v-on:click="play(item,$event)">
<a v-bind:ref="'key' + item.id" v-bind:href="item.url">
<!-- content -->
</a>
</li>
</ul>
and in Vue component you can use
var recordingModel = new Vue({
el:'#rec-container',
data:{
items:[]
},
methods:{
play:function(item,e){
// it contains the bound reference
console.log(this.$refs['key'+item.id]);
}
}
});
I found this snippet here. The idea is to go up the DOM node hierarchy until a __vue__ property is found.
function getVueFromElement(el) {
while (el) {
if (el.__vue__) {
return el.__vue__
} else {
el = el.parentNode
}
}
}
In Chrome:
Solution for Vue 3
I needed to create a navbar and collapse the menu item when clicked outside. I created a click listener on windows in mounted life cycle hook as follows
mounted() {
window.addEventListener('click', (e)=>{
if(e.target !== this.$el)
this.showChild = false;
})
}
You can also check if the element is child of this.$el. However, in my case the children were all links and this didn't matter much.
If you want listen an event (i.e OnClick) on an input with "demo" id, you can use:
new Vue({
el: '#demo',
data: {
n: 0
},
methods: {
onClick: function (e) {
console.log(e.target.tagName) // "A"
console.log(e.targetVM === this) // true
}
}
})
Exactly what Kamil said,
element = this.$el
But make sure you don't have fragment instances.
Since in Vue 2.0, no solution seems available, a clean solution that I found is to create a vue-id attribute, and also set it on the template. Then on created and beforeDestroy lifecycle these instances are updated on the global object.
Basically:
created: function() {
this._id = generateUid();
globalRepo[this._id] = this;
},
beforeDestroy: function() {
delete globalRepo[this._id]
},
data: function() {
return {
vueId: this._id
}
}

How to bind an AngularJS variable as a Polymer global?

I have an object variable in an Angular controller with some properties. I want to create a series of polymer elements that takes that variable an the name of the property and shows them in a specific format (depending of type and other attributes). Something like the next example:
<polymer-element name="x-property" attributes="data property">
<template>
{{data.labels[property]}}: {{data[property]}}
</template>
<script>
Polymer('x-property', {
data: {}
});
</script>
</polymer-element>
And then I use it as this:
<x-property data="{{person}}" property="firstName"></x-property>
That works just fine. But now I want to avoid to specify the attribute data in all the elements. Reading Polymer documentation I see that it is possible to have global variables. I followed the example created the app-globals element, as shown in the api guide but when I try to access the property, instead of having the object "person" I got the text "{{person}}"
<polymer-element name="app-globals" attributes="values">
<script>
(function () {
var values = {};
Polymer('app-globals', {
ready: function () {
this.values = values;
for (var i = 0; i < this.attributes.length; ++i) {
var attr = this.attributes[i];
values[attr.nodeName] = attr.value;
}
}
});
})();
</script>
</polymer-element>
<polymer-element name="x-property" attributes="property">
<template>
<app-globals id="globals" values="{{globals}}"></app-globals>
{{globals.data.labels[property]}}: {{globals.data[property]}}
{{globals.data}}
</template>
<script>
Polymer('x-property', {
});
</script>
</polymer-element>
So in my html I have:
<app-globals data="{{person}}"></app-globals>
<x-property property="firstName"></x-property>
And the result I get is just this:
:
{{person}}
Is there anyway I can make this work as it works in the first example?
Because attributes are only strings, this code values[attr.nodeName] = attr.value; cannot capture your object-valued data. Instead, JavaScript converts your object to a string, which is why you see [Object object].
Capturing objects with mustaches ({{ }}) is a special Polymer feature that you enable by publishing the property as an attribute (or listing it in the publish map in the prototype).
If, instead of making app-globals generic, we instead publish values and data directly, then we can make it work like so:
<polymer-element name="app-globals" attributes="values data">
<script>
(function () {
var values = {};
Polymer('app-globals', {
created: function() {
this.values = values;
},
dataChanged: function() {
this.values.data = this.data;
}
});
})();
</script>
</polymer-element>
<polymer-element name="x-property" attributes="property">
<template>
<app-globals values="{{globals}}"></app-globals>
{{globals.data.labels[property]}}: {{globals.data[property]}}
{{globals.data | json}}
</template>
<script>
Polymer('x-property', {
json: function(s) {
return JSON.stringify(s);
}
});
</script>
</polymer-element>
Your original code doesn't have the json filter, but otherwise we are again asking Javascript to put an object in a string context, and it will render [Object object].
http://jsbin.com/vusayo/10/edit

Categories