How can I listen to the scroll event in a sidebar [Bootstrap Vue] - vue.js

I am using the Sidebar component from BootstrapVue to render a navigation for my application.
As an example, I have 30 menu items in my navigation, so the content within the sidebar scrolls vertically. I would like to bind to that scroll event to dynamiclly add/remove some classes.
I have tried to create a custom scroll directive:
Vue.directive('scroll', {
inserted: function(el, binding) {
console.log('el', el);
let f = function(evt) {
if (binding.value(evt, el)) {
window.removeEventListener('scroll', f);
}
};
window.addEventListener('scroll', f);
}
});
...then register that to the component within my vue file:
<b-sidebar
v-scroll="handleScroll"
title="Menu"
shadow="lg"
backdrop
#change="$emit('sidebar-change')"
...
handleScroll() {
console.log('handleScroll');
},
The directive is being picked up properly, but my handleScroll method is firing when the main body is scrolling, not the sidebar.
In my directive, I am logging to see what element it thinks it's working with:
<div tabindex="-1" class="b-sidebar-outer">...</div>
Since Bootstrap is dynamiclly creating the markup for the overlay, that's the parent element -- looking closer, I believe I need to attach my directive to this:
<div class="b-sidebar-body">...</div>
That is the <div> that looks to be scrolling. However, since it is generated at runtime, I don't know how to hook into that.
I have also tried using #native.scroll="myMethod" on the component...no luck there either.
How can I listen for the scroll event within my sidebar component? Thank you for any suggestions!

Your scroll listener fires on the main window because the directive attached the event listener to window, and not the element.
To listen to scroll events on the contents of b-sidebar, the listener should be on an element inside the default slot of b-sidebar (not the b-sidebar itself).
Put a wrapper div inside b-sidebar's default slot, and style it to enable scrolling:
<template>
<b-sidebar>
<div class="wrapper">
<!-- your actual contents here -->
</div>
</b-sidebar>
</template>
<style>
.wrapper {
overflow: auto;
height: 100%;
}
</style>
Add the custom v-scroll directive on the wrapper div:
<div class="wrapper" v-scroll="handleScroll">
Update the custom directive to add the binding value as the event listener on the given element's scroll event:
Vue.directive('scroll', (el, binding) => {
let f = (evt) => {
if (binding.value(evt, el)) {
el.removeEventListener('scroll', f)
}
}
el.addEventListener('scroll', f)
})
demo

You need to check whether the event's target is the sidebar and only execute your function if it is.

Related

How to disable transition-group only on page load?

I have a transition-group that renders a div, and within that is a component with a v-for attribute that renders several items. I then have a button that adds a new item to the beginning of the array. That transition works perfectly.
The only thing I don't like, is that the entire list loads with the transition on page load, and I'd like to disable it only on page load. I've searched Stack and Google but couldn't find a way. Is there a way to do this, so that transitions still works on button click, but is disabled for page load?
<transition-group
name="item-list"
tag="div">
<item-row
v-for="item in items"
:key="item.id"
:item="item" />
</transition-group>
.item-list-enter-active,
.item-list-leave-active,
.item-list-move {
transition : 250ms cubic-bezier(0.59, 0.12, 0.34, 0.95);
transition-property: opacity, transform;
}
.item-list-enter {
opacity : 0;
transform: translateX(50px) scaleY(0.5);
}
.item-list-enter-to {
opacity : 1;
transform: translateX(0) scaleY(1);
}
.item-list-leave-active {
position: absolute;
}
.item-list-leave-to {
opacity : 0;
transform : scaleY(0);
transform-origin: center top;
}
I wish I could've found a more "Vue-y" way of handling this, however I ended up going this route. Essentially I added a class to the body and removed all transitions. Then on the created lifecycle of my component, I remove that class. This removes the transition on page load, but still keeps the transition on click of the button like I want.
You can dynamically change the name value of the transition-group. Maybe on page load have a value different from the value that has the correct class name that the CSS targets. Then in the mounted lifecycle hook you can change it back to the correct class name.
You need to bind the duration for transition-group
template:
<transition-group
:duration="duration"
name="item-list"
tag="div">
<item-row
v-for="item in items"
:key="item.id"
:item="item" />
</transition-group>
script:
data() {
return {
duration: 0,
items: [
{id: 1},
{id: 2}
]
}
},
methods: {
add() {
if(this.duration===0) this.duration = 250
this.items.push({id: 'xxx'})
}
}
In case anyone comes across this like I did.
I ended up achieving this by having a transitionsOn flag added to the data (didn't seem to matter what it was initialised to), and a computed name for the transition, i.e.
<transition-group :name="transitionName">
in computed, I then had, for a transition called 'flash'
computed: {
transitionName() {
return this.transitionsOn ? 'flash' : 'disabled';
},
},
I would then set this.transitionsOn = true when I wanted it to fire.
Took a lot of fiddling about to figure this out but it seems to work OK

How can I get CKEditor 5 "Link" dialog box to pin to custom DOM element instead of 'document.body'

I'm building a Vue.js web application. I'm using CKEditor in a form that is placed inside a modal window. By design, the user's focus is "trapped" in the modal. In CKEditor, when user clicks the "Link" icon in toolbar, the editor opens a dialog box and attaches the new DOM element to 'document.body'. With respect to the DOM, the "Link" dialog is now outside of trapped focus. The user cannot click or tab his way to the "Link" dialog input.
I dug into the ckeditor5-ui source and found relevant code in balloonpanelview.js. I've unsuccessfully tried to configure CKEditor based on https://ckeditor.com/docs/ckeditor5/latest/api/module_utils_dom_position-Options.html
In my Vue.js component, I have:
import ClassicEditor from '#ckeditor/ckeditor5-build-classic';
...
data: () => ({
editor: ClassicEditor,
editorConfig: {
toolbar: ['bold', 'italic', 'bulletedList', 'numberedList', 'link'],
},
...
})
...
I want the CKEditor "Link" dialog DOM element to be attached to a DOM element id that I specify.
In Vuetify dialog component is required to disable retain-focus
<v-dialog :retain-focus="false" />
There may be much time since you opened the issue. However... This issue was happening to me too. This is happening because Bootstrap modal trap the focus in the active modal. If you're using bootstrap-vue, do this.
In your <b-modal> add the prop no-enforce-focus.
no-enforce-focus is reactive. To properly apply this workaround you can use this prop with a variable, that detects when your CKeditor have focus. If have focus, disable the enforce focus. If doesn't have, restore it. You can apply it by the following way:
<template>
<b-modal
...
:no-enforce-focus="editorFocus">
<ckeditor
...
#focus="toggleEditorFocus(true)"
#blur="toggleEditorFocus(false)"
/>
</b-modal>
</template>
<script>
export default {
...
data () {
return {
editorFocus: false
}
},
methods: {
toggleEditorFocus (val = !this.editorFocus) {
setTimeout(() => {
this.editorFocus = val
}, 10)
}
}
}
</script>
I know the setTimeout is a tricky method, but at least is working now for me.

Quill.js doesn't work properly in a Vuetify v-dialog

I'm trying to use a Quill.js editor inside a Vuetify v-dialog, but the toolbar dropdowns are not closed when the user clicks outside the current opened dropdown.
I made a js Fiddle:
https://jsfiddle.net/6d7bef5n/
<div id="app">
<v-app>
<quill-editor v-model="content"></quill-editor>
<v-dialog v-model="dialog">
<quill-editor v-model="contentKo"></quill-editor>
</v-dialog>
<v-btn #click.stop="dialog = !dialog">Open Quill in a Modal</v-btn>
</v-app>
</div>
Vue.use(VueQuillEditor)
Vue.use(VueQuillEditor)
new Vue({
el: "#app",
data() {
return {
content: "I'm OK",
contentKo: "I'm Wrong, Toolbar dropdowns are not closing on blur",
dialog: false
}
}
});
It seems that the v-dialog component does something wrong on the events inside his content slot, probably for the open/close behavior, but didn't found what.
Thanks
As #MarlburroW pointed out Vuetify's VDialog components stops the propagation of the click event when the user clicks inside of the dialog.
https://github.com/vuetifyjs/vuetify/blob/master/packages/vuetify/src/components/VDialog/VDialog.js#L284
In my case I had a custom directive which detects clicks outside of the target element, for example for a dropdown component. This worked, but if you used such a component inside of Vuetify's dialog the custom directive would not work, because the VDialog stopped propagation of the click event.
Vuetify has its own outside click directive which they use for menus, selects...etc. It does not suffer from this issue.
https://github.com/vuetifyjs/vuetify/blob/master/packages/vuetify/src/directives/click-outside.ts
I had a look at the differences between Vuetify's directive and my own and the reason it works is that they use capturing instead of bubbling for the event listener.
The following codepen demonstrates it:
https://codepen.io/geersch/pen/LoLgYK
onClick = function (e) { console.log('The click event bubbled up.'); };
document.body.addEventListener('click', onClick, { capture: true });
// document.body.addEventListener('click', onClick, { capture: false });
dialog = document.querySelector('#dialog');
dialog.addEventListener('click', function (e) {
e.stopPropagation();
});
So I just changed my directive to use capturing too.
.quill-editor {
user-select: auto !important;
-moz-user-select: auto !important;
-webkit-user-select: auto !important;
-ms-user-select: auto !important;
}
Try it. It works for me.

Modifying Bootstrap 3's Collapse Animation with Animate.css

I managed to override the default "collapse in" animation for Bootstrap 3's collapse plugin but unable override the toggle back animation.
Basically I want it to fade in (which is doing now) and fade out on close which at the moment its defaulting to the default BS animation for that event.
Example jsfiddle
<div class="collapse animated fadeIn" id="collapseExample">
<div class="well">
...
</div>
</div>
After taking a look at the Bootstrap 3 documentation for "collapse" and This other question, I took over the events and managed to get it working.
I removed all animate.css classes from element.
jQuery to override the show and hide events generated by BS.
Create a class that would delay the transition on "hide".
Result jsfiddle
JS
$(function() {
var animateIn = 'animated fadeIn';
var animateOut = 'animated fadeOut collapsing-delay';
$('#collapseExample').on('show.bs.collapse', function () {
// do something…
$(this).addClass(animateIn).on('shown.bs.collapse',
function() {
$(this).removeClass(animateIn);
});
})
$('#collapseExample').on('hide.bs.collapse', function () {
// do something…
$(this).addClass(animateOut).on('hidden.bs.collapse',
function() {
$(this).removeClass(animateOut);
});
})
})
CSS
.collapsing-delay {
/* delay BS transition for animated fadeOut to show */
-webkit-transition-delay: 2s !important;
transition-delay: 2s !important;
}

say I have many divs and each div trigger an event when clicked

Dojo 1.7
say I have many divs and each div trigger an event when clicked. So when I cilck a div, dojo adds a class, say "clicked" to the div. But how can I set it so when I click another div, it removes the previous div class "clicked" and gives it to the div that I just clicked?
This is because if I clicked on one div it supposed to change its background and remove the background from the previously clicked div
Thanks!!!
You can put all these div in one container, for example
<div class='RadioDivContainer'>
<div> </div>
....
<div> </div>
<div>
Then do this in onclick event handler of divs:
dojo.query(".RadioDivContainer .clicked").forEach(function(node){
dojo.removeClass(node, "clicked");
});
dojo.addClass(evt.target, "clicked");
This is just show the idea how to implement it. You can change it to suit your case.
You can remove the clicked class from all the elements in the group before applying the clicked class to the newly-clicked element.
Using Dojo 1.7:
require([
'dojo/query',
'dojo/dom-class',
'dojo/on',
'dojo/dom',
'dojo/domReady!'
], function(query, dom_class, on, dom) {
var boxes = query('.box', dom.byId('#container')); // get elements inside #container
boxes.forEach(function(box) {
on(box, 'click', function() {
boxes.forEach(function(b) {
dom_class.remove(b, 'clicked');
});
dom_class.add(box, 'clicked');
});
});
});
Here's a fiddle.
You could also keep track of the last clicked element and remove the clicked class from that. You can see both examples in the fiddle.
You should enable hooks to your desired DOM elements with dojo.query, handle click events using dojo.on and assign/unassign classes with dojo/dom-class. Give the div elements a shared class to denote that they are part of this clickable unit, then listen for click events on all of them and assign classes as necessary. See this JSfiddle, using Dojo 1.7.4:
HTML
<div class="mutable"></div>
<div class="mutable"></div>
<div class="mutable"></div>
Javascript/Dojo
require(["dojo/query", "dojo/dom-class", "dojo/on", "dojo/domReady!"], function(query, domClass, on) {
on(query(".mutable"), "click", function(e) {
query(".mutable").forEach(function(node) {
domClass.remove(node, "clicked");
});
domClass.add(this, "clicked")
});
});
CSS
.mutable {
background-color:red;
}
.clicked {
background-color:green;
}
div {
border:2px solid black;
margin:5px;
}
This will also work with Dojo 1.8.x and 1.9.x.