Vue multiselect template slot clear does not work - javascript

i copied the code from async part of the documentation, because that's the 'X' to remove value i want.
here is my general component that i use in other vue components
<template>
<div>
<multiselect
v-model="items"
:options="filteredList"
:multiple="multiple"
:close-on-select="multiple ? false : true"
:show-labels="false"
:placeholder="placeholder"
track-by="id"
:label="label"
#input="inputChanged"
:internal-search="false"
#search-change="searchItems"
>
<template slot="clear" slot-scope="props">
<div class="multiselect__clear" v-if="items.length" #mousedown.prevent.stop="clearAll(props.search)"></div>
</template>
</multiselect>
</div>
</template>
<script>
export default {
model: {
prop: 'parentItems',
event: 'change',
},
props: ['multiple', 'list', 'placeholder', 'label', 'parentItems'],
data() {
return {
items: this.parentItems,
filteredList: this.list,
}
},
methods: {
searchItems(query) {
let q = latinize(query.toLowerCase().replace(/\s/g,''))
this.filteredList = this.list.filter(li => latinize(li[this.label].toLowerCase().replace(/\s/g,'')).includes(q))
},
inputChanged() {
this.$emit('change', this.items);
},
clearAll() {
this.items = this.multiple ? [] : null
},
},
}
</script>
everything works as desired, except the X to clear selection is never displayed.
i found the clear element in console, it has width of 255 and height of 0. i tried to put X between the div tags, like this
<template slot="clear" slot-scope="props">
<div class="multiselect__clear" v-if="items.length"
#mousedown.prevent.stop="clearAll(props.search)"
>
X
</div>
</template>
but it would display above the select input field. also changing the height attribute in dev console just made clear space above input field.
what am i missing?

vue-multiselect does nothing special with the clear slot other than render it before the input tags. It leaves the styling and behavior/implementation entirely up to the end user.
In addition, the example from the docs you linked poorly implements the slot, as the provided slot has no contents, so the div won't be visible or clickable, making it effectively useless. Not to mention, it uses the obsolete (pre-2.6) slot syntax of Vue, which would cause warnings on the browser console if using the development build of Vue.
Solution
The clear slot should look like this:
<multiselect v-model="value"
:options="options"
multiple
taggable
#tag="addTag">
<template v-slot:clear>
<button v-if="value.length" class="multiselect__clear" #click="clearSelectedTags">
Ⓧ Clear selection
</button>
</template>
</multiselect>
demo

Thanks to tony19 I went and inspected the part of documentation I mentioned in the question.
I found out that they use different code for the example, so to attain desired effect, I need to add following code to my css.
.multiselect__clear {
position: absolute;
right: 41px;
height: 40px;
width: 40px;
display: block;
cursor: pointer;
/*z-index: 2;*/
}
.multiselect__clear:after, .multiselect__clear:before {
content: "";
display: block;
position: absolute;
width: 3px;
height: 16px;
background: #aaa;
top: 12px;
right: 4px;
cursor: pointer;
}
.multiselect__clear:before {
transform: rotate(45deg);
}
.multiselect__clear:after {
transform: rotate(-45deg);
}

Related

Vue.js style scoped behaviour on setAttribute class, why does it not show?

I'm creating a commenting function for a text editor in a Vue.js project. The commenting function will highlight a portion of text and put a comment card on the right side, similar to Google comment system in docs.
As I was working on the highlight function of the feature I realized that the setAttribute class only worked on a global class and not scoped for the component the text is displayed in. Why is this? What is the functionality behind this? I'm curious about how it works under the hood.
tl;dr element.setAttribute('class', 'classname') on a dynamically created element (document.createElement('element')) does not work if the class is in scoped styles, why?
<template>
<div>
<p #click="highlight">
{{ text }}
</p>
</div>
</template>
<script>
export default {
name: 'Paper',
data() {
return {
text: "Some lorem ipsum"
}
},
methods: {
highlight() {
var selected = window.getSelection().getRangeAt(0)
var selectedText = selected.extractContents()
console.log(selected)
console.log(selectedText)
var span = document.createElement("span")
console.log(span)
span.setAttribute('class', 'highlight-text')
span.appendChild(selectedText)
selected.insertNode(span)
}
}
}
</script>
<style scoped>
div {
box-shadow: rgba(60, 64, 67, 0.15) 0px 1px 3px 1px;
height: 1123px;
width: 794px;
padding: 96px;
margin: 100px
}
p {
line-height: 1.4;
}
</style>
<style>
.highlight-text {
background-color: yellow;
}
</style>
Also, if you have any tips on how I can improve the code it is very much appreciated!
Vue will replace the scoped css class name with a unique data attributes and set this on the element.
I.e the setAttribute tries to add a class name which actually does not exists.
Example from docs:
https://vue-loader.vuejs.org/guide/scoped-css.html#mixing-local-and-global-styles

Webcomponents is re-initializing every time while working with Vue js

I have created a webcomponent for a generic input boxes that i needed across multiple projects.
the design functionality remains same only i have to use switch themes on each projects.so i have decided to go on with webcomponents.One of the projects is based on Vue Js.In Vue js the DOM content is re-rendered while each update for enabling reactivity. That re-rendering of vue template is reinitializing my custom webcomponent which will result in loosing all my configurations i have assigned to the component using setters.
I know the below solutions. but i wanted to use a setter method.
pass data as Attributes
Event based passing of configurations.
Using Vue-directives.
using v-show instead of v-if
-- Above three solutions doesn't really match with what i am trying to create.
I have created a sample project in jsfiddle to display my issue.
Each time i an unchecking and checking the checkbox new instances of my component is creating. which causes loosing the theme i have selected. (please check he active boxes count)
For this particular example i want blue theme to be displayed. but it keep changing to red
JSFiddle direct Link
class InputBox extends HTMLElement {
constructor() {
super();
window.activeBoxes ? window.activeBoxes++ : window.activeBoxes = 1;
var shadow = this.attachShadow({
mode: 'open'
});
var template = `
<style>
.blue#iElem {
background: #00f !important;
color: #fff !important;
}
.green#iElem {
background: #0f0 !important;
color: #f00 !important;
}
#iElem {
background: #f00;
padding: 13px;
border-radius: 10px;
color: yellow;
border: 0;
outline: 0;
box-shadow: 0px 0px 14px -3px #000;
}
</style>
<input id="iElem" autocomplete="off" autocorrect="off" spellcheck="false" type="text" />
`;
shadow.innerHTML = template;
this._theme = 'red';
this.changeTheme = function(){
this.shadowRoot.querySelector('#iElem').className = '';
this.shadowRoot.querySelector('#iElem').classList.add(this._theme);
}
}
connectedCallback() {
this.changeTheme();
}
set theme(val){
this._theme = val;
this.changeTheme();
}
}
window.customElements.define('search-bar', InputBox);
<!DOCTYPE html>
<html>
<head>
<title>Wrapper Component</title>
<script src="https://unpkg.com/vue"></script>
<style>
html,
body {
font: 13px/18px sans-serif;
}
select {
min-width: 300px;
}
search-bar {
top: 100px;
position: absolute;
left: 300px;
}
input {
min-width: 20px;
padding: 25px;
top: 100px;
position: absolute;
}
</style>
</head>
<body>
<div id="el"></div>
<!-- using string template here to work around HTML <option> placement restriction -->
<script type="text/x-template" id="demo-template">
<div>
<div class='parent' contentEditable='true' v-if='visible'>
<search-bar ref='iBox'></search-bar>
</div>
<input type='checkbox' v-model='visible'>
</div>
</script>
<script type="text/x-template" id="select2-template">
<select>
<slot></slot>
</select>
</script>
<script>
var vm = new Vue({
el: "#el",
template: "#demo-template",
data: {
visible: true,
},
mounted(){
let self = this
setTimeout(()=>{
self.$refs.iBox.theme = 'blue';
} , 0)
}
});
</script>
</body>
</html>
<div class='parent' contentEditable='true' v-if='visible'>
<search-bar ref='iBox'></search-bar>
</div>
<input type='checkbox' v-model='visible'>
Vue's v-if will add/remove the whole DIV from the DOM
So <search-bar> is also added/removed on every checkbox click
If you want a state for <search-bar> you have to save it someplace outside the <search-bar> component:
JavaScript variable
localStorage
.getRootnode().host
CSS Properties I would go with this one, as they trickle into shadowDOM
...
...
Or change your checkbox code to not use v-if but hide the <div> with any CSS:
display: none
visibility: hidden
opacity: 0
move to off screen location
height: 0
...
and/or...
Managing multiple screen elements with Stylesheets
You can easily toggle styling using <style> elements:
<style id="SearchBox" onload="this.disabled=true">
... lots of CSS
... even more CSS
... and more CSS
</style>
The onload event makes sure the <style> is not applied on page load.
activate all CSS styles:
(this.shadowRoot || document).getElementById("SearchBox").disabled = false
remove all CSS styles:
(this.shadowRoot || document).getElementById("SearchBox").disabled = true
You do need CSS Properties for this to work in combo with shadowDOM Elements.
I prefer native over Frameworks. <style v-if='visible'/> will work.. by brutally removing/adding the stylesheet.

Binding a Component property in Vue

Good day everyone! I'm currently learning vue and I'm following a youtube tutorial on building an image carousel from scratch. Here's the code for the Image Carousel parent component:
<template>
<div class = "slides">
<div class = "slides-inner">
<div v-for= "slide in slides">
<Slide v-bind:slide= "slide"></Slide>
</div>
</div>
</div>
<script>
import Slide from './Slide';
export default{
data(){
return{
slides:[
{ src:'src/assets/slide1.jpg' },
{ src:'src/assets/slide2.jpg' },
{ src:'src/assets/slide3.jpg' },
{ src:'src/assets/slide4.jpg' },
{ src:'src/assets/slide5.jpg' }
]
}
},
components: {
Slide
}
}
</script>
<style scoped>
.slides {
align-items: center;
background-color: #666;
color: #999;
display: flex;
font-size: 1.5rem;
justify-content: center;
min-height: 10rem;
}
</style>
and here's the code for the individual image slides:
<template>
<div class="slide">
{{slide.src}}
</div>
</template>
<script>
export default {
data(){
return{}
},
props: {
slide: ['slide']
}
}
</script>
<style scoped>
</style>
The v-bind on the v-for loop on the parent Image Carousel component is supposed to bind the slide.src property to the current slide being looped so that it will display the image in the browser but what I'm getting is a blank browser screen and an error that says the right value of the operator at v-bind is not an object although the tutorial I'm following works exactly as it should with this same code so I'm wondering what I'm doing wrong.
In your slide component, it should be
export default {
data(){
return{}
},
props: {
slide: Object
}
}
You can check valid props type in Vue document
Please also see the Vue Style Guide as prop definitions should be as detailed as possible. An Essential Requirement by the creators of Vue. See Vue Style Guide.

Dynamic image is not loaded via :src

I am working on a simple Vue app, using vue-cli and webpack for that purpose.
So basicly i have 2 components, a parent and a child component ~
like this:
<template>
<div class="triPeaks__wrapper">
<div class="triPeaks">
<tri-tower class="tower"></tri-tower>
<tri-tower class="tower"></tri-tower>
<tri-tower class="tower"></tri-tower>
</div>
<div class="triPeaks__line">
<tower-line :towerLine="towerLineCards" />
</div>
<tri-pack />
</div>
</template>
the towerLineCards is the important thing there, it is a prop that is passed to the tower-line component, it is basicly a array with 10 elements, it is a array with 10 numbers that are shuffled, so it can be something like that:
[1,5,2,6,8,9,16,25,40,32]
this array is create via beforeMount on the lifecycle.
On the child component:
<template>
<div class="towerLine-wrapper">
<div class="towerLine">
<img v-for="index in 10" :key="index" class="towerLine__image" :src="getImage(index)" alt="">
</div>
</div>
</template>
<script>
export default {
props: {
towerLine: {
type: Array,
required: true
}
},
method: {
getImage (index) {
return '#/assets/images/cards/1.png'
}
}
}
</script>
<style lang="scss">
.towerLine {
display: flex;
position: relative;
top: -90px;
left: -40px;
&__image {
width: 80px;
height: 100px;
&:not(:first-child) {
margin-left: 3px;
}
}
}
</style>
the issue is with the :src image that i am returning via the getImage(), this way it is not working. If i change to just src it works just fine, i did this way just to test, because the number in the path should be dynamic when i got this to work.
What is wrong with this approach? any help?
Thanks
Firstly, you should use a computed property instead of a method for getImage().
And to solve the other problem, you could add require(YOUR_IMAGE_PATH) when you call your specific image or put it inside /static/your_image.png instead of #/assets/images/cards/1.png.

Keep a parent element the same size before and after conditionally showing a button in it with vue.js

Using vue.js (and quasar framework), I have a card component. When an event is triggered a button at the bottom of the card is shown. When the button appears, the size of the card increases due to the height of the button which is added. I find this ugly and would prefer the size of the card to be the same before and after having a button.
I tried with some <br> before the button is added to compensate for the height difference, but this is clumsy and does not work properly when I animate the appearance of the button with a fade-in e.g.
As the card(s) will contain various content(size), making a fixed size for card will not really work.
How can I have the same size of my card before and after showing the button?
Quick fix: you must know button height before. Then nest it to element with same height achieved with min-height property :
<div id="button-container" style="min-height: /* your button height */">
<button>Hidden yet</button>
</div>
It is not very elegant way. Use it only if you are not able to use visibility: hidden on button, instead of display: none, as #musicformellons suggest in comment.
I think, this example demonstrates your problem:
new Vue({
el: '#app',
data: {
canShow: false
},
methods: {
toggleButton () {
this.canShow = !this.canShow
}
},
created () {
setInterval(function () {
this.toggleButton()
}.bind(this), 500)
}
})
.bordered {
position: absolute;
border: 2px solid black;
}
<div id="app">
<div class="bordered">
<p>Lorem Ipsum, bla, bla, bla...</p>
<button v-if="canShow">I am just troublemaker</button>
</div>
</div>
<script src="https://unpkg.com/vue"></script>
And I think this is the most elegant, "true Vue way" solution. Moreover, with this solution you need not to know button height before...
new Vue({
el: '#app',
data: {
visibility: false
},
methods: {
toggleButton () {
this.visibility = !this.visibility
},
logIt () {
console.log('button clicked')
}
},
created () {
setInterval(function () {
this.toggleButton()
}.bind(this), 1000)
}
})
.bordered {
position: absolute;
border: 2px solid black;
}
.animate-me {
transition: all .4s;
}
.is-hidden {
opacity: 0;
}
<div id="app">
<div class="bordered">
<p>Lorem Ipsum, bla, bla, bla...</p>
<!-- Render it always, but change visibility as needed instead -->
<button
class="animate-me"
:class="{'is-hidden': visibility}"
#click="logIt"
:disabled="visibility"
>
I am just troublemaker
</button>
</div>
</div>
<script src="https://unpkg.com/vue"></script>
You could give the css of the button position: absolute and the card position: relative, and then fiddle around with the bottom; left; top; right; position settings of the button.

Categories