Vue component interop with vanilla code - javascript

I'm trying to fit Vue.js inside an existing project that doesn't use Vue. But I could not find any resource or documentation on how I can write a Vue component that have an API that can be used by code outside of the Vue world. Everything is focused on apps built with Vue from top to bottom.
var MyV = Vue.extend({
template: `
<div>
<h4>{{ message }}</h4>
<ul>
<li v-for="item in items">
{{item}}
<button v-on:click="remove(item)">-</button>
</li>
</ul>
<button v-on:click="add">add</button>
</div>
`,
data() {
return {
message: 'Hello Vue.js!',
items: ['foo', 'bar'],
};
},
methods: {
add() {
this.items.push(Math.random());
},
remove(item) {
this.items.splice(this.items.indexOf(item), 1);
}
}
});
var v = new MyV({
el: c1
});
<script src="https://unpkg.com/vue#2.5.8/dist/vue.js"></script>
<div id="c1"></div>
What I already figured out just by playing around:
From outside of Vue, I can just mutate the v instance and the view changes according.
// everything works
v.message = "Hi there!";
v.items.push('hello');
v.items.add();
How I'm I supposed to listen to a "onchange" event? I can se a few ways of implementing this, but there's an "official" way?
Setting stuff is pretty straightforward, I can just set any property, or initialize it while instantiating, but how do I can get data back from this view?
Let's say I'm building a dropdown menu with Vue, I can populate this "component" just by setting a property.
If there's like a "submit" button inside this view, how I can notify the code outside of the view the user clicked on it?
If the view provides UI for the user modify its internal state (this.data) how code outside can be notified that the data has been modified, and it can then get the content of it again?

In the same way that you can call v.add() from outside the Vue code, you can watch value members from outside:
v.$watch('items', (newValue) => { console.log("Items changed!", newValue); });
You could also use v.$emit and v.$on to send and receive custom events, since every Vue instance is an event bus.

Related

Svelte component created with JavaScript not receiving reactive updates

I dynamically create a component on button click with the following JS:
<script>
export let order;
function toggleModal(an_order){
modal_open.set(!$modal_open);
const modal = new OrderModal({
target: document.querySelector('.modal_container-' + order.id),
props: {
order: an_order
},
});
}
</script>
However whenever I update the order object it does not reactively reflect in the created component.
If I put the component directly in the html instead of making it dynamic like this:
<div class="modal_container-{order.id} fixed">
<OrderModal {order} />
</div>
<div class="max-w-xs w-full overflow-hidden rounded-lg shadow-md bg-white cursor-pointer" on:click="{() => toggleModal(order)}">
Then it works correctly and the elements are reactively updated.
Is there a way to make components created by JavaScript update reactively?
*** Updating to show how it is inserted ***
The function that does the update:
function deleteItem(){
order.order_items.splice(index, 1);
$order_list = $order_list;
}
As you can see I explicitly do an assignment to trigger the update which as specified works when the component is not created through javascript.
I don't know what's the context of your update function and where it's located/called from, but when creating a component programmatically/imperatively, triggering a rerender by assignment doesn't work. Instead you need to use the $set method on the component instance (docs: https://svelte.dev/docs#$set):
<script>
export let order;
let modal; // <- top level variable so it can be used in the update function
function toggleModal(an_order){
modal_open.set(!$modal_open);
modal = new OrderModal({
target: document.querySelector('.modal_container-' + order.id),
props: {
order: an_order
},
});
}
// ...
function deleteItem(){
order.order_items.splice(index, 1);
// imperatively update the order
modal.$set({order: $order_list});
}
// if you want updates of the `order` prop to propagate to the modal once it's set, you can do
$: modal && modal.$set({order: order});
</script>
The reason this doesn't work is because the compiler will create all the links between reactive elements during compilation. Your Modal component does not exist at that time so it is not possible to do so. This also explains why adding the Modal directly works.
One way to work around this is by using a store and pass this store into the modal.
In general in Svelte, you will not use the code as you have it. A more Svelte way to do it would be to use a bool to track if the Modal has to be shown or not:
<script>
let showModal = false
function toggleModal() {
showModal = !showModal
}
</script>
{#if showModal}
<Modal />
{/if}

Vue.JS - listen to click on component

I'm fairly new to Vue.JS and currently having an issue listening to a click event on a component.
JS:
Vue.component('photo-option', {
data: function () {
return {
count: 0
}
},
props: ['src'],
template: `
<img :src=src v-on:click="$emit('my-event')" />
`
});
HTML:
<photo-option :src=uri v-for='uri in aboutUsPhotos' v-on:my-event="foo" />
...where foo is a method on my main Vue instance.
The above is based on the Vue.JS docs for handling component events, and I can't see what I'm doing wrong. The alert doesn't fire, and there's no errors in the console.
Before I found those docs, I also tried simply adding v-on:click='...' to both the JS (i.e. the template) and the HTML, each with no success.
What am I doing wrong?
[EDIT]
This is happening because the code is picked up by a lightbox script and has its DOM position changed. So presumably the binding/event attachment is being lost.
Does Vue have any way of allowing for this, perhaps by 'reinitialising' itself on an element, or something?

How do I correctly use my inherited datas from my parent component?

I'm a newbie with VueJS and I'm setting up a rather simple website where the user can change some elements from the website, such as the website's background image.
From the admin part of the website, the user can upload an image that will be used as one of the page's background. The value is stored in a Rails API, and, when a user gets to the visitor's part, VueJS calls the settings from Rails, then displays it where I want them. I use Vue-Router, so I make the call for the settings in my App.vue and transmit the received datas to the <router-view> component. I decided to do so to load all the datas only once instead of loading them for each page change.
Here is how it looks:
App.vue
<template>
<div id="app">
<transition :name="transitionName">
<router-view :settings="settings" :me="bio"></router-view>
</transition>
</div>
</template>
<script>
export default {
data: function () {
return {
settings: null
}
},
created () {
this.$http.secured.get('/settings')
.then(response => {
this.settings = response.data
})
.catch(error => this.setError(error))
}
}
</script>
front/Landing.vue
<template>
<div id="landing" :style="{background: this.settings.landing_bg.url}">
<p>{{ this.settings.landing_bg.url }}</p>
<!-- Some other elements, not related -->
</div>
</template>
<script>
export default {
props: ['settings'],
created () {
console.log(this.settings)
}
}
</script>
<style scoped>
</style>
I have several problems with this configuration:
The first one is that I get errors saying that VueJS can't read "landing_bg" of null
But VueJS has no problem displaying the image's path in the <p> right under it
console.log(this.settings) returns null when I reload the page, but will display the settings correctly if I go to another page then back. However, no background image is set.
I tried to declare with datas() what structure this.settings will have, but VueJS tells me that it doesn't like how there are two settings declared.
I guess this is a problem with asynchronous loads, but how should I handle that? Should I use VueX?
Thank you in advance
First of all, this is redundant in your :style, change it to:
:style="{ background: settings.landing_bg.url }"
By the way, unless your landing_bg.url looks like this: url('bg_url') you might want to create a computed property:
computed: {
bgURL() { return `background: url('${this.settings.landing_bg.url}')` }
}
You might also have to add a v-if in that div to render it only after settings have been loaded. That error pops up because settings is still null when the component is created.
<div id="landing" :style="bgURL" v-if="settings">
I tried to declare with datas() what structure this.settings will have, but VueJS tells me that it doesn't like how there are two settings declared.
No, declare it on the props, like this
props: {
'settings': {
type: Object,
default: () => ({
landing_bg: {
url: '' //here you can specify a default URL or leave it blank to just remove the error
}
})
},
},

Vue.js DOM added after initialization don't interact

I have an HTML page updated with ajax contents.
I'm using Vue.js for some dynamic front-end events.
Dynamically added elements don't interact with the Vue.js instance, even if I try to forceUpdate.
How could I do that?
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.0/vue.js"></script>
<div id="app">
<button v-on:click="click()">click will console something</button>
</div>
<script>
var app = new Vue({
el: '#app',
methods: {
click: function() {
console.log('clicked')
},
}
});
setTimeout(function() {
$('#app').append('<button v-on:click="click()">click here wont do anything</button>');
app.$forceUpdate();
}, 1000);
</script>
That is not how you add elements in Vue. Your contract with Vue is that it will control the DOM and you will provide it a model that describes how things should appear and work.
Let go of jQuery. If you want a button to conditionally appear, have a variable that controls its appearance, and set that variable to true.
If you really, really have to deal with DOM being added outside of Vue, then you will need to call new Vue on the newly added elements to put them under Vue's control.

execute action (javascript function) after ember template is rendered

I have very small web page with emberjs, where I want to show some item list and openlayers map for them, and another map for selected item.
Something like that:
<script type="text/x-handlebars" id="list">
<div class="list">
<div id="list_map"></div>
</div>
{{outlet}}
</script>
<script type="text/x-handlebars" id="list/item" >
<div class="item">
<div id="item_map"></div>
</div>
</script>
<script>
function showListMap() {
listMap = new ol.Map({target:'list_map'});
}
function showItemMap() {
itemMap = new ol.Map({target:'item_map'});
}
</script>
There is no problem to display map for list:
model: function(params) {
var content = [];
var url = 'http://localhost:8080/app/list';
$.ajax({
url: url,
success: function(surveys) {
content.pushObjects(surveys);
showListMap();
}
});
return content;
}
and I have action in item controller that is executed, when opening selected item, but if I try to create item map there (in controllers action) it fails (afaik because there is no such div at that moment in DOM).
So if I could execute action or my function after div is already add to DOM, it should work.
And question would be, how to execute something after template is added to DOM, or that's completely wrong way to do such stuff (than what would be correct ember way)?
I can't say much with seeing full code. But to execute some code after the DOM is rendered you schedule a function on the the run loops afterRender queue.
Ember.run.scheduleOnce('afterRender', this, function() {
//The div should be available now.
});
But if you really need to touch the DOM I recommend you wrap your map code in a component. A component gets a didInsertElement where you can write your maps initialization code.
var component = Em.Component.extend({
setup: function() {
//Do you map init here.
}.on('didInsertElement')
});
There unfortunately isn't a really good route or controller hook that fires off after a page has already rendered. I believe the reason for this is that the developers of Ember think it is an anti-pattern to directly talk to the DOM.
That being said, I think it sometimes is quite handy for complex UI on otherwise static web pages. If you want to do some sort of jquery or use the DOM API after a route has rendered, you can do the following in your route file (as #Dainius correctly points out)
routeName.js
import Route from '#ember/routing/route';
import jQuery from 'jquery';
export default class myRouteFile extends Route {
manipulateDom = function() {
$("#myDiv").css( "color", "red" );
}
init() {
this._super(...arguments)
Ember.run.scheduleOnce('afterRender', this, this.manipulateDom)
}
}

Categories

Resources