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}
Related
I'm a newbie of Vuejs.
In vue2, I already have verified whether component is displayed by specific trigger.
However, I cannot verify whether above displayed component is disappeared after another trigger.
I tried to verify wrapper.findComponent('...').exists() is false.
However, I got true when component should be disappeared.
At first, I suspect another trigger does not work well,
so I called wrapper.html(), and there is no component that I want to verify.
That's why, trigger works well, probably.
My question is, as I said at title, How to verify component existance when flag is toggled.
Below is my code.
code for test.
test('After date pick, picker is closed', async() => {
let node = document.createElement('body')
const wrapper = mount(App, {
localVue,
vuetify,
attachTo: node
})
// modal window does not appear yet.
expect(wrapper.findComponent('.v-picker').exists()).toBe(false)
// `enable` is set to true. Then, modal window is displayed.
// and verification got true
const menu = wrapper.getComponent(DateMenu)
await menu.setData({ enable: true })
expect(wrapper.findComponent('.v-picker').exists()).toBe(true)
// $emit input event cause `enable` set to false
const picker = wrapper.getComponent('.v-picker')
await picker.vm.$emit('input', '2020-01-01')
// html() returns result in which there is no elements related to toggled component
console.log(wrapper.html())
expect(menu.vm.$data.enable).toBe(false)
// test fail findComponent found a component
expect(wrapper.findComponent('.v-picker').exists()).toBe(false)
})
As reference, source code (short version).
<template>
<!-- v-model="enable" controls modal window is displayed or not-->
<v-menu v-model="enable">
<template v-slot:activator="{ on, attrs }">
<v-text-field
v-model="date"
v-bind="attrs"
v-on="on"
>
</v-text-field>
</template>
<!-- This is displayed and verified existance in test. -->
<v-date-picker v-model="date" #input="input"> </v-date-picker>
</v-menu>
</template>
<script>
export default {
data: () => {
return {
// enable controls modal window is displayed or not.
// true: displayed
// false: disappear
enable: false,
};
},
methods: {
input(date) {
this.enable = false
}
}
};
</script>
I think in your test, you encounter an update applied by Vue, which is asynchronous behavior.
So after changing the reactive property (enable = false), you should wait for Vue to update DOM by adding this line:
await Vue.nextTick();
expect(wrapper.findComponent('.v-picker').exists()).toBe(false);
Vuetify renders its dialogs, menus, pickers (etc.) outside of the application wrapper, that's why wrapper.html() doesn't help you.
I believe the correct way is to use attachTo option, so you can mount your component inside the div with the data-app attribute.
If that doesn't work for you, you can also set data-app attribute on body:
let node = document.createElement('body');
node.setAttribute("data-app", true);
I'm creating some custom components in svelte and I didn't understood how to pass events as props such as on:click, on:mouseup, on:whatever, I tried in the followings way but they didn't work:
// button.svelte
// OnClick it's a prop but in this way, I need to pass every single event manually
<div on:click={OnClick} ></div>
or
// button.svelte
// I tried even in this way but it didn't really work
<div {...$$restProps} {...$$props} ></div>
The hierarchy is App => Outer => Inner
App:
<script>
import Outer from './Outer.svelte';
function handleMessage(event) {
alert(event.detail.text);
}
</script>
<Outer on:message={handleMessage}/>
Outer:
<script>
import Inner from './Inner.svelte';
</script>
<Inner on:message />
Inner:
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
function sayHello() {
dispatch('message', {
text: 'Hello!'
});
}
</script>
<button on:click={sayHello}>
Click to say hello
</button>
Now, this to me is event bubbling up. If you're trying to do it in a reverse way.. I'm not so sure you can achieve that. Data is passed down, events bubble up.
When it comes to trying to send all events in one big go, there seems to be this svelte github issue created to allow on:* syntax. Sadly it isn't yet implemented.
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.
I'm creating a modal that displays information about a specific user and it's triggered by clicking on a picture. The modal should be able to fire up from different components. My approach is as follows (I use React with Redux):
create store with a "modal" property that's set to a reducer "modalReducer"
modal: modalReducer
modal is an object with the following properties: showModal=false, user=undefined, image=undefined
When a div containing picture is clicked, the action is being dispatched that sets showModal to true (with username and url image link as arguments).
export var openModal = (user, image) => {
return {
type: 'OPEN_MODAL',
user,
image
}
}
For the actual modal I use a foundation reveal modal and in its render method I check if modal.showModal == true and use jQuery to open it up.
Now, toggling the modal works as charm but I cannot use the modal.user and modal.image properties inside of the render method as it prints the following error message:
Uncaught Error: findComponentRoot(..., .0.2.0.0.1): Unable to find element. This probably means the DOM was unexpectedly mutated (e.g., by the browser), usually due to forgetting a when using tables, nesting tags like , , or , or using non-SVG elements in an parent. Try inspecting the child nodes of the element with React ID ``.
I'm almost certain the problem lies within the ProfileModal.jsx component. Any idea what I'm doing wrong?
export class ProfileModal extends React.Component {
componentDidMount () {
var elem = new Foundation.Reveal($('#profile-modal'));
$('#profile-modal').on('closed.zf.reveal',()=>{this.props.dispatch(actions.hideModal())});
}
render () {
var {modal} = this.props;
if (modal.showModal) {
$('#profile-modal').foundation('open');
}
return(
<div>
<div id="profile-modal" className="reveal large">
<div className="modal-container">
<div className="modal-picture" style={{backgroundImage: `url(${modal.image})`}}></div>
<p>{modal.user}</p>
...
I am having an issue with required PropTypes throwing errors. I would expect the PropType to throw an error if the Component was was being directly rendered. Here is a small sample of what I am trying to achieve.
You'll notice that the Button prop has a required PropType of handle click.
But I want the implementation of a Modal to be as simple as possible.
And since I don't have the context of Modal I can not bind the handleClick method directly to the Button so I pass the Button in as child and map over the children adding the handleClick method to the child component. This works pretty well besides throwing the error for Button because <Button> gets called and checked before it truly gets rendered.
I have tried to do this a few other ways as well using Higher Order Components which worked as well. But the implementation seemed convoluted and tedious this seems like a much simpler way to just generate a Modal when it is needed. You don't need any props you just pass in a child component and it will add the click handler.
It would be awesome to either Bypass the proptypes check until it is actually rendered in Modal Component or maybe there is a simpler way, all feed back is welcomed.
https://jsfiddle.net/kriscoulson/00xLw0up/1/
var Button = (props) =>
<button onClick={props.handleClick}>
{props.children}
</button>
Button.propTypes = {
handleClick: React.PropTypes.func.isRequired
}
class Modal extends React.Component {
openModal () {
console.log('Opening Modal.....')
}
childrenWithProps () {
return React.Children.map(this.props.children,(child) => React.cloneElement(child, {
handleClick: this.openModal
})
);
}
render () {
return (
<div>
{this.childrenWithProps()}
</div>
);
}
}
var App = () =>
<Modal>
<Button>Launch Modal</Button>
</Modal>
ReactDOM.render(
<App/>,
document.getElementById('container')
);
<script src="https://facebook.github.io/react/js/jsfiddle-integration-babel.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>