I have been trying for 2 days now to integrate a table into svelte where I can select with mouseDown and dragging.
I´m new in js and svelte and did a few courses.
But at this moment is not possible for me to get this working.
Thats what i try to build in svelte: TableSelect
Hope anyone can help me.
Many thanx, Chris
It turned out slightly more complicated than suggested in my comment.
The idea is to listen to mousedown and mouseup events on the entire window (through <svelte:window>) to toggle an isDrag state on/off, listen to mouseenter events on table cells, then toggle the cells on/off only if isDrag is on.
Additionally, you will have to listen to the mousedown event on cells as well, in case the dragging is started inside a cell.
The on/off state of a single cell can be shown visually with a class:selected attribute.
<script>
let columns = new Array(5) // number of columns
let rows = new Array(3) // number of rows
let state = new Array(rows.length*columns.length).fill(false)
let isDrag = false
const beginDrag = () => {
isDrag = true
}
const endDrag = () => {
isDrag = false
}
const toggle = (r, c) => {
state[r*columns.length+c] = !state[r*columns.length+c]
}
const mouseHandler = (r, c) => (e) => {
if (isDrag || e.type === 'mousedown') {
toggle(r, c)
}
}
</script>
<style>
td {
width: 80px;
height: 80px;
background-color: pink;
}
.selected {
background-color: blue;
}
</style>
<svelte:window on:mousedown={beginDrag} on:mouseup={endDrag} />
<table>
{#each rows as _row, r}
<tr>
{#each columns as _column, c}
<td on:mousedown={mouseHandler(r, c)} on:mouseenter={mouseHandler(r, c)} class:selected="{state[r*columns.length+c]}"></td>
{/each}
</tr>
{/each}
</table>
Demo REPL
Related
I dynamically create a number of rows in a table.
Each row has seven columns with the same controls in each columns.
1st column is a toggle meant to disable the textboxes or dropdowns in the other six columns.
How can I dynamically create the event listeners and their respective functions to give each toggle their intended behaviour?
Here is the solution,
const toggles = document.querySelectorAll('.toggle');
toggles.forEach(toggle => {
toggle.addEventListener('click', function(event) {
const toggleRow = event.target.closest('tr');
const textboxes = toggleRow.querySelectorAll('.textbox');
const dropdowns = toggleRow.querySelectorAll('.dropdown');
if (toggle.checked) {
textboxes.forEach(textbox => textbox.setAttribute('disabled', true));
dropdowns.forEach(dropdown => dropdown.setAttribute('disabled', true));
} else {
textboxes.forEach(textbox => textbox.removeAttribute('disabled'));
dropdowns.forEach(dropdown => dropdown.removeAttribute('disabled'));
}
});
});
Click here to view detailed explanation about EventListeners.
hope you will get an idea about the code...
I am creating a list of divs, which was created with map.
function renderButtons(){
const options = [...Array(10).keys()] // returns [0,1,2...9]
return _.map(options, (option)=> renderOption(option))
}
function renderOption(option:number){
return (
<div className="option-container" onClick={() => setLowerContainerVisible(true)}>
<img alt="" src={"./images/feedback-icons/icon-"+option.toString()+".svg"}/>
{option+1}
</div>
)
}
this renders a list of divs, and I was able to change each div background, when hover, like this:
.option-container{
width: 76px;
height: 100px;
background-color: #7777ff;
display: flex;
}
.option-container:hover{
background-color: #adadf3;
}
I wish to be able to click on a div, and change its background color to white. everything I try will change the background of all the 10 divs to white. How can I make it so only the clicked one is changed?
I suggest that you use renderOption and renderButtons as two components rather than plain functions. In the RenderButtons component, you can use some state to maintain which item is clicked, and within RenderOption you can control whether the background color is white or not based on wehther or not the current rendered button is the clicked option. In your .map() method, you can use component rather than a function call <RenderOption option={option} ... />.
See example below:
const {useState} = React;
function RenderButtons() {
const [clickedItem, setClickedItem] = useState(-1);
return Array.from(
Array(10).keys(),
option => <RenderOption isClicked={clickedItem === option} option={option} setClicked={setClickedItem}/>
);
}
function RenderOption({isClicked, option, setClicked}) {
const handleClick = () => {
// setLowerContainerVisible(true) / other code to run when you click
setClicked(option); // set to current option
}
return (
<div className={"option-container " + (isClicked ? "clicked" : "")} onClick={handleClick}>
{option+1}
</div>
)
}
ReactDOM.render(<RenderButtons />, document.body);
.option-container {
width: 76px;
height: 100px;
background-color: #7777ff;
display: flex;
}
.option-container.clicked, .option-container:hover {
background-color: #adadf3;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script>
The className is a little messy as it involves a ternary, to clean this up it might be worth looking into using a node package such as classnames which allows you to easily build a list of classes based on conditions.
Do it in the event listener function:
<div className="option-container" onClick={highlightAndsetLowerContainerVisible}>
function highlightAndsetLowerContainerVisible(event){
event.preventDefault();
setLowerContainerVisible(true)
event.currentTarget.style.backgroundColor = "red";
}
You then might also want to reset the background color of the other divs
I'm struggling with this and I have no idea how to proceed. I want to capture mouse event only when the user stops with mousemove for certain time and it's inside specific element.
const { fromEvent } = rxjs;
const { debounceTime, tap, filter } = rxjs.operators;
const square = document.querySelectorAll("#square");
let isCursorOverSquare = true;
const move$ = fromEvent(square, "mousemove");
const enter$ = fromEvent(square, "mouseenter");
const leave$ = fromEvent(square, "mouseleave");
enter$.pipe(
tap(() => isCursorOverSquare = true)
).subscribe();
leave$.pipe(
tap(() => isCursorOverSquare = false)
).subscribe();
move$
.pipe(
debounceTime(2000),
filter(() => isCursorOverSquare)
)
.subscribe(
(e) => {
console.log(e.target);
});
#square {
width: 200px;
height: 200px;
display: block;
border: 1px solid black;
}
<script src="https://unpkg.com/rxjs#6.3.2/bundles/rxjs.umd.min.js"></script>
<div id="square"></div>
The thing I can't figure out is, how to skip the console.log, if the user moves from square to outside (i.e. handle the logic only, when user is with cursor inside the square).
EDIT:
I managed to work it, but it's not the "RxJs" way. Now I'm setting the isCursorOverSquare variable to true and false and then I use filter operator. Is there a "nicer" more reactive way, to handle this?
So if i understand your question correctly you want to:
Track all mouse movements (mousemove event stream - fromevent)
After movement stops for a certain time (debounce)
Verify it is within a bounding box (filter)
So depending on performance you can either always event the mousemoves or only start mousemove eventing after entering the square using the .switchMap() operator:
enter$
.switchMap(_ => $moves
.debounceTime(2000)
.takeUntil(leave$)
)
.subscribe(finalMouseMoveEventInSquare => {});
The issue that you have is the last mousemove event is triggered when the cursor is still in the square, but the debounce delays the observable until after the cursor has left the square. You can solve this issue by only taking the observable until the mouse has left the square. Here is the complete code for this answer:
<head>
<style>
#square {
width: 200px;
height: 200px;
display: block;
border: 1px solid black;
}
</style>
</head>
<body>
<div id="square"></div>
<script src="https://unpkg.com/rxjs#6.3.2/bundles/rxjs.umd.min.js"></script>
<script>
const { fromEvent } = rxjs;
const { debounceTime, repeat , takeUntil } = rxjs.operators;
const square = document.getElementById("square");
const move$ = fromEvent(square, "mousemove").pipe(debounceTime(2000));
const enter$ = fromEvent(square, "mouseenter");
const leave$ = fromEvent(square, "mouseleave");
move$.pipe(takeUntil(leave$), repeat()).subscribe((e) => console.log(e.target));
</script>
</body>
The repeat operator is necessary as otherwise once the mouse has left the square the first time, the observable will not be repeated when the mouse next enters the square. If your intended behaviour is for the observable to stop emitting after the mouse has left the square for the first time, feel free to remove the repeat operator. Hope this helps you, let me know if you have any questions!
Trying to create a drag n drop implementation from an Rxjs course example, but its not working correctly. Some time the box is dragged back to original position some times it just get stuck. Here is the plunkr
https://plnkr.co/edit/9Nqx5qiLVwsOV7zU6Diw?p=preview
the js code:
var $drag = $('#drag');
var $document = $(document);
var $dropAreas = $('.drop-area');
var beginDrag$ = Rx.Observable.fromEvent($drag, 'mousedown');
var endDrag$ = Rx.Observable.fromEvent($document, 'mouseup');
var mouseMove$ = Rx.Observable.fromEvent($document, 'mousemove');
var currentOverArea$ = Rx.Observable.merge(
Rx.Observable.fromEvent($dropAreas, 'mouseover').map(e => $(e.target)),
Rx.Observable.fromEvent($dropAreas, 'mouseout').map(e => null)
);
var drops$ = beginDrag$
.do(e => {
e.preventDefault();
$drag.addClass('dragging');
})
.mergeMap(startEvent => {
return mouseMove$
.takeUntil(endDrag$)
.do(moveEvent => moveDrag(startEvent, moveEvent))
.last()
.withLatestFrom(currentOverArea$, (_, $area) => $area);
})
.do(() => {
$drag.removeClass('dragging')
.animate({top: 0, left: 0}, 250);
})
.subscribe( $dropArea => {
$dropAreas.removeClass('dropped');
if($dropArea) $dropArea.addClass('dropped');
});
function moveDrag(startEvent, moveEvent) {
$drag.css(
{left: moveEvent.clientX - startEvent.offsetX,
top: moveEvent.clientY - startEvent.offsetY}
);
}
If I remove the withLatestFrom operator, then dragging of div always work fine, but without this I cannot get the drop feature implemented.
Problem one: Some time the box is dragged back to original position some times it just get stuck.
Answer: you should replace order of chain, ".do" before ".withLatestFrom" like this:
const drops$ = beginDrag$
.do( e => {
e.preventDefault();
$drag.addClass('dragging');
})
.mergeMap(startEvent => {
return mouseMove$
.takeUntil(endDrag$)
.do(mouseEvent => {
moveDrag(startEvent, mouseEvent);
})
.last()
.do((x) => {
console.log("hey from last event",x);
$drag.removeClass('dragging')
.stop()
.animate({ top:0, left: 0}, 250);
}
)
.withLatestFrom(currentOverArea$, (_, $area) => {
console.log('area',$area);
return $area;
});
Problem two: drop and drag outside not working correctly.
Answer: because of mouse event causing by "pointer-events" is not clearly.
In Css File, at:
.dragable .dragging {
background: #555;
pointer-events: none;
}
This is not Enough, the "mouseout" (or "mouseleave") still working, so when you drag box and drop. it happening the same time event "mouseover" and "mouseout". So the drag area never change color.
What to do ?:
make it better by clear every mouse event from the target element. In this case, it is div#drag.dragable.dragging. Add only this to CSS and problem is solve.
div#drag.dragable.dragging {
pointer-events: none;
}
(Holly shit, it take me 8 hours to resolve this. Readmore or see Repo at: Repository
)
I'd like to detect a click inside or outside a div area. The tricky part is that the div will contain other elements and if one of the elements inside the div is clicked, it should be considered a click inside, the same way if an element from outside the div is clicked, it should be considered an outside click.
I've been researching a lot but all I could find were examples in jquery and I need pure javascript.
Any suggestion will be appreciated.
It depends on the individual use case but it sounds like in this example there are likely to be other nested elements inside the main div e.g. more divs, lists etc. Using Node.contains would be a useful way to check whether the target element is within the div that is being checked.
window.addEventListener('click', function(e){
if (document.getElementById('clickbox').contains(e.target)){
// Clicked in box
} else{
// Clicked outside the box
}
});
An example that has a nested list inside is here.
You can check if the clicked Element is the div you want to check or not:
document.getElementById('outer-container').onclick = function(e) {
if(e.target != document.getElementById('content-area')) {
console.log('You clicked outside');
} else {
console.log('You clicked inside');
}
}
Referring to Here.
you can apply if check for that inside your click event
if(event.target.parentElement.id == 'yourID')
In Angular 6 and IONIC 3, I do same as here:
import {Component} from 'angular/core';
#Component({
selector: 'my-app',
template: `
<ion-content padding (click)="onClick($event)">
<div id="warning-container">
</div>
</ion-content>
`
})
export class AppComponent {
onClick(event) {
var target = event.target || event.srcElement || event.currentTarget;
if (document.getElementById('warning-container').contains(target)){
// Clicked in box
} else{
// Clicked outside the box
}
}
}
This working fine on web/android/ios.
It might be helpful for someone, Thanks.
Try this solution it uses pure javascript and it solves your problem. I added css just for better overview... but it is not needed.
document.getElementById('outer-div').addEventListener('click', function(){
alert('clicked outer div...');
});
document.getElementById('inner-div').addEventListener('click', function(e){
e.stopPropagation()
alert('clicked inner div...');
});
#outer-div{
width: 200px;
height: 200px;
position: relative;
background: black;
}
#inner-div{
top: 50px;
left: 50px;
width: 100px;
height: 100px;
position: absolute;
background: red;
}
<div id="outer-div">
<div id="inner-div">
</div>
</div>
I came up with a hack for this that's working well for me and that might help others.
When I pop up my dialog DIV, I simultaneously display another transparent DIV just behind it, covering the whole screen.
This invisible background DIV closes the dialog DIV onClick.
This is pretty straightforward, so I'm not going to bother with the code here. LMK in the comments if you want to see it and I'll add it in.
HTH!
closePopover () {
var windowBody = window
var popover = document.getElementById('popover-wrapper') as HTMLDivElement;
windowBody?.addEventListener('click', function(event){
if(popover === event.target) {
console.log("clicked on the div")
}
if(popover !== event.target) {
console.log("clicked outside the div")
}
})
}
}
I recently needed a simple vanilla JS solution which solves for:
Ignoring specific selectors including whether a parent contains one of these selectors
Ignoring specific DOM nodes
This solution has worked quite well in my app.
const isClickedOutsideElement = ({ clickEvent, elToCheckOutside, ignoreElems = [], ignoreSelectors = [] }) => {
const clickedEl = clickEvent.srcElement;
const didClickOnIgnoredEl = ignoreElems.filter(el => el).some(element => element.contains(clickedEl) || element.isEqualNode(clickedEl));
const didClickOnIgnoredSelector = ignoreSelectors.length ? ignoreSelectors.map(selector => clickedEl.closest(selector)).reduce((curr, accumulator) => curr && accumulator, true) : false;
if (
isDOMElement(elToCheckOutside) &&
!elToCheckOutside.contains(clickedEl) &&
!didClickOnIgnoredEl &&
!didClickOnIgnoredSelector
){
return true;
}
return false;
}
const isDOMElement = (element) => {
return element instanceof Element || element instanceof HTMLDocument;
}
In React you can use useClickOutside hook from react-cool-onclickoutside.
Demo from Github:
import { useClickOutside } from 'use-events';
const Example = () => {
const ref1 = React.useRef(null);
const ref2 = React.useRef(null);
const [isActive] = useClickOutside([ref1, ref2], event => console.log(event));
return (
<div>
<div ref={ref1} style={{ border: '1px dotted black' }}>
You are {isActive ? 'clicking' : 'not clicking'} outside of this div
</div>
<br />
<div ref={ref2} style={{ border: '1px dotted black' }}>
You are {isActive ? 'clicking' : 'not clicking'} outside of this div
</div>
</div>
);
};
Live demo