Move component to another DOM node using ember-maybe-in-element - javascript

I have a use case where I can move a child component to different DOM location within same page/route dynamically.
home.hbs
<div class="container">
<div class="content">
<!-- Place where I want to place Child -->
</div>
</div>
<Parent></Parent>
Parent.hbs
<h1>This is parent component</h1>
<Child></Child>
child.hbs
Hello World
child.js
const mainContainer = document.querySelector('.container .content');
const myElm = this.element.querySelector('[data-child-content]');
mainContainer.appendChild(myElm);
I want to use ember-maybe-in-element addon instead of using appendChild.

The in-element helper renders the block content outside of the regular flow, into a DOM element given by its destinationElement positional argument.
child.js
get destinationElement() {
return document.querySelector('.container .content');
}
child.hbs
{{#in-element this.destinationElement}}
<div>Hello World</div>
{{/in-element}}

Related

How to match a parent node based on the text of the child node in React Testing Library

I am trying to write some tests for two lists of users (users and members, identical markup, different semantic content) in React Testing Library. Details of each user are inside of a span element.
I have this DOM tree:
<div>
<span>
<div>
<b>Text 1</b>
</div>
<div>
<!-- other elements here -->
</div>
</span>
</div>
<div>
<span>
<div>
<b>Text 2</b>
</div>
<div>
<!-- other elements here -->
</div>
</span>
</div>
<div>
<span>
<div>
<b>Text 3</b>
</div>
<div>
<!-- other elements here -->
</div>
</span>
</div>
I want to match the span element (because it handles the onClick logic) based on the text inside the b child element (because it contains user's name and last name).
I tried doing it this way:
const getUsersLists = (doc) => ({
usersList: doc.querySelector('.users-users-list'),
membersList: doc.querySelector('.members-users-list'),
});
const getUser = (name, list) => (
screen.findByText(name), { selector: 'span' }, { container: list })
);
test('users exist', () => {
const { usersList } = getUsersLists(document);
const sampleUser = await getUser('Text 1', usersList);
expect(sampleUser).toBeInTheDocument();
});
The problem:
Those matchers successfully find the Text 1 text if I remove the { selector: 'span' } options object from the getUser method. As soon as I tell RTL to look for a span element it can't find anything.
I also want to avoid any more document.querySelector and class / ID (including data-testid) references.

How can DOM manipulation like wrapping text in a paragraph element be achieved using only Vue.js?

I have a Vue component which has a contenteditable div that lets users type in a message. When the user first attempts to create a message, I am using jQuery to wrap the text in a <p> tag. I cannot understand how this could be achieved using Vue.js alone...
Vue.js component
<template>
<div id="Message" contenteditable="true" #focus="formatMessage" #keydown="formatMessage" #keyup="formatMessage" #keypress="formatMessage">
</div>
</template>
<script>
import $ from 'jquery'
formatMessage: function(event) {
if ($("#Message > p").length === 0) { // if no <p> element when user interacts with div
$("#Message").contents().eq(0).wrap("<p />"); // then wrap a <p> tag around the first child content
}
}
Is it possible to do this using just Vue.js so I don't have to load the jQuery library for simple DOM manipulation (which may cause an issue with Vue's virtual DOM being out-of-sync with jQuery's changes)?
Before formatMessage():
<div id="Message" contenteditable="true">
I started typing here
</div>
After formatMessage():
<div id="Message" contenteditable="true">
<p>I started typing here</p>
</div>
Is it possible/better to try to do it using Vue's virtual DOM? Could I somehow use createElement to create a new p tag and then update its contents with what the user is typing? Maybe thats not the way the Virtual DOM works I'm not sure.
You can use v-if and duplicate the code a little if you want to achieve something similar
<template>
<div v-if="shouldWrap === false" contenteditable="true" #focus="formatMessage" #keydown="formatMessage" #keyup="formatMessage" #keypress="formatMessage">
</div>
<p v-else>
<div contenteditable="true" #focus="formatMessage" #keydown="formatMessage" #keyup="formatMessage" #keypress="formatMessage">
</div>
</p>
</template>
<script>
export default {
data () {
return {
shouldWrap: false
}
},
methods: {
formatMessage() {
this.shouldWrap = true
}
}
}
</script>
But probably trying to match the styling of a p should also work.
Do not use JQuery-like DOM manipulation in VUE, VUE is data driven framework, you need to store some data in component to trigger layout, for example
<template>
<div contenteditable="true" #focus="formatMessage" #keydown="formatMessage" #keyup="formatMessage" #keypress="formatMessage">
<!-- wrap 'p' tag, if 'shouldWrap'-->
<p v-if="shouldWrap">{{content}}</p>
<!-- without wrap-->
<template v-else>{{content}}</template>
</div>
</template>
<script>
export default {
data () {
return {
shouldWrap: false,
content:'' // text, you want to display inside div
}
},
methods: {
formatMessage() {
this.shouldWrap = true
}
}
}
</script>

On click outside of some element in vue without packages

I am wondering how can we hide all shown elements on click outside from those elements in vue:
<button #click="div1=true">Show div 1</button>
<div v-if="div1">DIV 1</div>
<button #click="div2=true">Show div 2</button>
<div v-if="div2">DIV 2</div>
How can I hide all divs besides div 1 if I click on div 1 or hide all divs on click on some random part of the page?
How does vuetify handle it?
Vue way is to use this.$el (DOM element of current component), you can change this.$el and use any other HTMLElement
<div class="modal" #click="handleClick">
<div class="modal-dialog">
<div class="modal-content">
...
</div>
</div>
And method should look like this
handleClick(e: Event): void {
if (e.target instanceof HTMLElement && !this.$el.contains(e.target)) {
this.$emit("hide"); //fires only on click outside
}
}
It's easy to detect click outside of the element if you know how event bubbling works in DOM.
Hiding other divs works perfectly well when you put state of every div into the components's state.
Here is an example: https://codesandbox.io/s/0480m38mww?fontsize=14&module=%2Fsrc%2FApp.vue
Learn more about Event Bubbling here - https://javascript.info/bubbling-and-capturing
I used this process so Home component would know that outside my Nav component has been clicked. It helps that I have my nav inside my Home Component.
My Home component template:
<div>
<nav-bar ref="nav"></nav-bar>
<div #click="useHideNavMethod()">
So I use ref="nav" to use methods from the Nav in my Home component. This means I can use the hideNav() method (which resides in Nav).
I have purposely put the nav outside of this div with the click method, so anywhere clicked other than Nav would initiate the useHideNavMethod() function.
In my Nav component template I have:
<nav id="nav">
which is referenced when I use ref="nav".
So inside my hideNav() I have this.styleNavLi.display = 'none'; so it would hide the nav bar.
So all I need to do to use that method in Home is to use:
useHideNavMethod() {
this.$refs.nav.hideNav();
}
Use div1 = !div1 for toggle view,
Or use div1 = true for one time only.
<template>
<div>
{{ div1 }}
<button #click="div1 = !div1">Show div 1</button>
<div v-if="div1">DIV 1</div>
{{ div2 }}
<button #click="div2 = !div2">Show div 2</button>
<div v-if="div2">DIV 2</div>
</div>
</template>
<script>
export default {
data() {
return {
div1: true, // set default value as you want
div2: false,
};
},
};
</script>

How to update a slot in Vue.JS

I have a Vue component simplified below.
Here is the template
<template>
<slot></slot>
</template>
The slot may contain HTML, which is why I decided to use a slot rather than a prop which I would simply bind to. I'd like to keep it that way.
I have a method that gets new HTML from the server. I'd like to use this new HTML to update the slot. I'm not sure if slots are reactive and how I can accomplish this.
I can view the default slot using this.$slots.default[0], but I don't know how to update it with a string of HTML content. Simply assigning the string to the element is obviously incorrect, to .innerHtml does not work because it isn't an available function, and to .text doesn't work. I assume that even though the text element exists on the slot object, the element properties take precedence.
Per suggestion in comments, I've tried this along with a computer property.
<span v-html="messageContent"><slot></slot></span>
But now the problem is that it overwrites the slot passed to me.
How can I reactively update a slot with new HTML in Vue.JS?
I think your issue comes from a misunderstanding of how <slot> inherently works in VueJS. Slots are used to interweave content from a consuming parent component into a child component. See it as a HTML equivalent of v-bind:prop. When you use v-bind:prop on a component, you are effectively passing data into a child component. This is the same as slots.
Without any concrete example or code from your end, this answer is at best just guess-work. I assume that your parent component is a VueJS app itself, and the child component is the one that holds the <slot> element.
<!-- Parent template -->
<div id="app">
<custom-component>
<!-- content here -->
</custom-component>
</div>
<!-- Custom component template -->
<template>
<slot></slot>
</template>
In this case, the app has a default ground state where it passes static HTML to the child component:
<!-- Parent template -->
<div id="app">
<custom-component>
<!-- Markup to be interweaved into custom component -->
<p>Lorem ipsum dolor sit amet.</p>
</custom-component>
</div>
<!-- Custom component template -->
<template>
<slot></slot>
</template>
Then, when an event is fired, you want to replace that ground-state markup with new incoming markup. This can be done by storing the incoming HTML in the data attribute, and simply using v-html to conditionally render it. Let's say we want to store the incoming markup in app's vm.$data.customHTML:
data: {
customHTML: null
}
Then your template will look like this:
<!-- Parent template -->
<div id="app">
<custom-component>
<div v-if="customHTML" v-html="customHTML"></div>
<div v-else>
<p>Lorem ipsum dolor sit amet.</p>
</div>
</custom-component>
</div>
<!-- Custom component template -->
<template>
<slot></slot>
</template>
Note that in contrast to the code you have tried, the differences are that:
It is the parent component (i.e. the consuming component) that is responsible for dictating what kind of markup to pass to the child
The child component is as dumb as it gets: it simply receives markup and renders it in the <slot> element
See proof-of-concept below:
var customComponent = Vue.component('custom-component', {
template: '#custom-component-template'
});
new Vue({
el: '#app',
data: {
customHTML: null
},
components: {
customComponent: customComponent
},
methods: {
updateSlot: function() {
this.customHTML = '<p>Foo bar baz</p>';
}
}
});
.custom-component {
background-color: yellow;
border: 1px solid #000;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app">
<h1>I am the app</h1>
<button type="button" #click="updateSlot">Click me to update slot content</button>
<custom-component>
<div v-if="customHTML" v-html="customHTML">
</div>
<div v-else>
<p>Lorem ipsum dolor sit amet.</p>
</div>
</custom-component>
</div>
<!-- custom-component template -->
<script type="text/template" id="custom-component-template">
<div class="custom-component">
<h2>I am a custom component</h2>
<!-- slot receives markup set in <custom-component> -->
<slot></slot>
</div>
</script>
Below is my solution though I don't like this opinion (load html into slot directly in current component level) because it breaks the rules for the slot. And I think you should do like this way (<component><template v-html="yourHtml"></template></component>), it will be better because Slot will focus on its job as Vue designed.
The key is this.$slots.default must be one VNode, so I used extend() and $mount() to get the _vnode.
Vue.config.productionTip = false
Vue.component('child', {
template: '<div><slot></slot><a style="color:green">Child</a></div>',
mounted: function(){
setTimeout(()=>{
let slotBuilder = Vue.extend({
// use your html instead
template: '<div><a style="color:red">slot in child</a></div>',
})
let slotInstance = new slotBuilder()
this.$slots.default = slotInstance.$mount()._vnode
this.$forceUpdate()
}, 2000)
}
})
new Vue({
el: '#app',
data() {
return {
test: ''
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<child><h1>Test</h1></child>
</div>

Conditionally display Element using Aurelia

So I have my auth class injected into my main.js:
import {Auth} from 'auth';
import {inject} from 'aurelia-framework';
#inject(Auth)
export class App {
constructor(auth) {
this.auth = auth;
}
get isLoggedIn() { return this.auth.isLoggedIn; }
}
so in my app.html
<form>
<!-- form login elements -->
</form>
how do I make this element conditionally display based on my app getter function.
You can use if.bind to conditionally bind your DOM elements.
<form>
<div if.bind="auth.isLoggedIn">
<!--this DOM element will be bind only if auth.isLoggedIn is true-->
</div>
</form>
Optionally, you can also use show.bind but that will only hide your DOM elements. Where as if.bind will not render it on your page.
If you need to remove element completely from markup when condition is not met, you can use if.bind (as #Pratik Gajjar answered):
<template>
<div if.bind="auth.isLoggedIn">
<!--this DOM element will be bind only if auth.isLoggedIn is true-->
</div>
</template>
If you need to conditionally set display: none on element, you can use show.bind:
<template>
<div show.bind="auth.isLoggedIn">
<!--this DOM element will be shown only if auth.isLoggedIn is true-->
</div>
</template>
For details have a look at http://aurelia.io/hub.html#/doc/article/aurelia/framework/latest/cheat-sheet/6.
So I created a value converter:
export class CssdisplayValueConverter {
toView(value) {
return value ? 'none' : 'display';
}
}
Then in my app.html:
<require from='css-display'></require>
<form css="display: ${isLoggedIn() | cssdisplay}"></form>
Boom, done.
You can use if.bind and show.bind for binding an element by checking a condition

Categories