Angular Auto-scrolling with drag and drop - javascript

I'm new to Angular in the last few weeks. For a work project, I want the window to scroll up or down when I drag a DOM element to the top or bottom of the window.
I'm following an example for integrating dragula and dom-autoscroller into my application, since dragula doesn't have autos-crolling built in. I'm following this example from an angular application which uses touch and another on codepen which uses dragula and auto-scroller together.
From the examples, I created this logic:
const drake = this.dragulaService.find('MyDragGroup').drake;
this.scroll = autoScroll(
window,
{
margin: 30,
maxSpeed: 25,
scrollWhenOutside: true,
autoScroll() {
return this.down && drake.dragging;
}
});
Originally, I put this logic in a parent component, but the drake came back undefined. When I put it right on one of the components that handles the drag and drop, I get the following TypeError in the console:
...followed by an ERROR CONTEXT, which applies to the template associated to the component.
<div class="drag-things" dragula="MyDragGroup" [(dragulaModel)]="currentGroup.MyDragThings">
<div *ngFor='let item of currentGroup.MyDragThings'>
<app-drag-thing [currentDragThing]='item'></app-drag-thing>
</div>
</div>
I checked with the documentation and I don't think I'm doing anything unusual. The primary difference between the way I'm doing things and the way the examples and the documentation give is I'm using the directive to create the drake (using [(dragulaModel)], where they choose to create the drake within the component.
Could this be causing the problem? Why would it be causing the problem? How do I get my auto-scrolling to work? Is there just a better way to accomplish this in general?

So the answer is... Don't use Dragula if you need auto-scrolling. Instead use Angular Material's CDK drag and drop. It has auto-scrolling built in. Check it out:
https://material.angular.io/cdk/drag-drop/overview

Related

Drag Drop CDK: keep showing dragged element inside starting list

I'm playing with cdk Drag and Drop cause I need it to create a POC for work.
I started with this code from the documentation website.
I saw that every time I drag an element outside its list, said element is hidden until I drop it in the same list or in another one.
So tell me if I'm wrong, but it seems that the dragged element is not a copy of the list item, but instead it's the element itself.
In conclusion I want the list to not change its layout while I'm dragging one of its elements.
Long story short:
CURRENT BEHAVIOUR
EXPECTED BEHAVIOR
I didn't found anything to do this in the cdk documentation.
In your stackblitz change in cdk-drag-drop-connected-sorting-example.css
change
.cdk-drag-placeholder {
opacity: 0;
}
into: (or just remove it)
.cdk-drag-placeholder {
/* opacity: 0; */
}
the fact its disappearing is due to css styling of the placeholder.
If you want to disable Sorting. Have a look HERE!
Also needed this and couldn't find something that the library supports.
My solution was to hide the whole original cdkDropList and replace it with a look alike.
It won't work for all, for me it works because each cdkDrag is in its own cdkDropList.
Dunno if you still need a solution for this, but there are 2 events that can help you create a solution for your problem.
cdkDropListExited / cdkDropListEntered
CdkDragEnter / CdkDragExit
The events mentioned are triggered when the dragged element is removed (which is what you wanna avoid) or when it has entered a container.
!NOTE These events are triggered when you move over the target container (but you click is still pressed) => you are still dragging
My solution was adding a custom placeholder div, which I hide/show when these events are triggered.
Hope this is clear enough for you
Peace
The expected behavior is still not implemented till now and there's an open issue on Github regarding this.
Someone provided a workaround for that and it actually worked for me.
Shortly speaking, the solution is adding the dragged item to the source list temporarily, until the item reaches is final destination, where he then removes the temp item from the source list.
Here's a link to the solution which worked for me:
https://stackblitz.com/edit/angular-krmecd
The key code to the solution
<mat-list
cdkDropList
[cdkDropListData]="sItems"
cdkDropListSortingDisabled
[cdkDropListEnterPredicate]="noReturnPredicate"
(cdkDropListExited)="onSourceListExited($event)"
(cdkDropListEntered)="onSourceListEntered($event)">
<mat-list-item *ngFor="let item of sItems" cdkDrag [cdkDragData]="item">{{ item.name }}</mat-list-item>
</mat-list>
noReturnPredicate() {
return false;
}
onSourceListExited(event: CdkDragExit<any>) {
this.sItems.splice(event.container.getItemIndex(event.item) + 1, 0, { ... event.item.data, temp: true });
}
onSourceListEntered(event: CdkDragEnter<any>) {
remove(this.sItems, { temp: true });
}

Best way in Angular 6 to check that a scrollable area is scrolled max bottom

I try to find a good working solution for angular 6 to check the scrollable host component that its max scrolled bottom or not.
I cant find any working example for a function that checks if a custom scrollable container (like a div or what ever) is max bottom scrolled or not. I found something about IntersectionObserver but i think this is not that what i need. Tryed also couple of vanilla JS functions, but maybe i just dont have the skills to make them work in angular.
Here a Stackblitz: https://stackblitz.com/edit/mark6-messenger
Please keep in mind that i need this for a open Source Project. The <ng-container is in my ase the scrollable container: https://github.com/DevMonkeysDE/ngx-mark6/blob/master/projects/mark6-lib/src/lib/messenger/messenger-history.html#L1
It must work without that people need to add later on the coponent any functions, ids or something else. The logic must work component inside.
Finally figured out your issue... But I did it with pure JavaScript which is working fine for me. I checked in your code. Please do the below changes and do let me know is that you was looking for.
1. site-messanger.component.ts
#HostListener('scroll', ['$event'])
public scrollHandler(event) {
let obj = document.getElementById('markMessageHistory');
let objScrollHeight = Math.round((obj.scrollTop) * 100) / 100;
if ( (objScrollHeight) === (obj.scrollHeight - obj.offsetHeight - 0.55)) {
console.log('object to bottom');
}
}
2. site-messanger.template.html
<mark6-messenger-history id="markMessageHistory" (scroll)="scrollHandler($event)">
<mark6-messenger-message [messages]="messages" [avatarMe]="false" [avatarOthers]="true"></mark6-messenger-message>
</mark6-messenger-history>
I have added id to your component. Now if I scroll your component till bottom it will show me console log that your component is at bottom.
Note - The 0.55 is added by considering your margin.

What does ui.draggable.draggable means?

I am new to javascript and have stated to learn javascript. I came across a piece of code .I would like to know the use and meaning of
ui.draggable.dragabble in the code shown below
drop:function(e,ui){
var drag = ui.draggable;
$(this).droppable('option', 'accept', drag);
drag.css({'top':$(this).css('top'),'left':$(this).css('left')});
drag.draggable('option', 'revert', function(){return false});
var drop_index=$(this).attr("id").split('_')[1];
I would also like to know the sites to learn about drag and drop in javascript,in a better way.
Any help is appreciated in advance.
"ui.draggable" refers to the object containing all the elements that are currently being dragged on the page.
The function
drop: function(e, ui) {
}
is executed when a draggable object is dropped on a droppable element. You can refer to the jquery-ui API documentation here: http://api.jqueryui.com/category/interactions/ for drag and drop functionalities. It is to the point and apt. Also do check their demos. Start with the demos for better grasp of it.
For drag and Drop:
HTML5 already has attributes 'draggable="true"' that you can add to your elements. You can then attach events like handleDrag, handleDrop etc so you get the required functionality. Check it here: https://www.html5rocks.com/en/tutorials/dnd/basics/
jQueryUI has interactive widgets that can be added to your page elements. It is pretty simple to grasp. Refer to the above link mentioned for exploring. You can use touch-punch library http://touchpunch.furf.com/ to make jqueryUI work on mobile devices as well.
You can write your own pure javascript drag and drop function. I found one here: https://github.com/lukasolson/drag-n-drop-js .
I know only these 3 ways for drag-and-drop. There might be others as well.

How can I position an Angular Material panel dialog relative to a button?

According to a discussion at Github one cannot position a standard dialog (api), but panel dialogs (api) can be positioned.
A simplified demo shows that this is true:
var position = this._mdPanel.newPanelPosition().bottom(0).right(0);
The Angular Material docs show a method that allows positioning relative to the clicked element (or whatever is passed in). I'm unable to get this to work, however.
var target = el.target;
var position = this._mdPanel.newPanelPosition().relativeTo(target);
Passing in hard values for .top() and .right(), for example, allows positioning relative to the viewport. I can't get positioning relative to the clicked element, though. How is this supposed to work?
I've been working with Angular Material for the past several months and still find the documentation lacking, so forgive the length of this post as my pseudo documentation on the issue. But here is what I do know:
I've only been able to get the panel location to work, relative to a target element, by chaining the addPanelPosition function onto the relativeTo function as such:
var position = this._mdPanel
.newPanelPosition()
.relativeTo(ev.target)
.addPanelPosition('align-start', 'below') // or other values
(in this case, ev is the $event object passed by ng-click)
I was able to track down the acceptable parameters for addPanelPosition and they are the following:
Panel y position only accepts the following values:
center | align-tops | align-bottoms | above | below
Panel x Position only accepts the following values:
center | align-start | align-end | offset-start | offset-end
Interstingly enough, in the Angular Material demo, they use the this._mdPanel.xPosition.ALIGN_START and this._mdPanel.yPosition.BELOW properties which simply resolve to strings as their x and y values for the addPanelPosition function. I've always gone straight with the string values. However, using string values could be problematic if the development of this feature is still in flux and they change the acceptable string values.
I'll point out one more issue I've seen.
Another trick they use in the demo is to specify a class name in the relativeTo function instead of a target element, then place that class on the target element itself. The reason this approach can be helpful is because the $event object from ng-click can provide different target elements based on what exactly was clicked. For example, clicking the button <div> is going to give a different target than clicking the <span> text inside the button. This wil cause your panel to shift locations unless you provide the additional functionality not to do so.
Codepen
I took their demo and really cut it down to size to focus on this issue. You can see the updated codepen here
As I post in a comment, here you can see it working on a plunker.
My solution is very close the to #I think I can code answer. However, in my answer, instead of a menu, a <md-dialog> is displayed when the button is clicked, as it's requested in the OP.
Besides the working plunker with a dialog, there is no much to add to the good #I think I can code answer. As it's shown in the angular-material md-panel demo, the key here is to set the position of the panel relative to the button. To do that (like in the angular-material demo), we can use a specific css class (demo-dialog-open-button in my example) to find the target element. this is a tricky thing in my opinion...but it works well for this use case (it's also well explained in the other answer).
Code for reference, see the plunker for the complete details:
html (note the css class added to the button):
<md-button class="md-primary md-raised demo-dialog-open-button" ng-click="ctrl.showDialog($event)">
Dialog
</md-button>
JS controller.
var position = this._mdPanel.newPanelPosition()
.relativeTo('.demo-dialog-open-button')
.addPanelPosition(this._mdPanel.xPosition.ALIGN_START, this._mdPanel.yPosition.BELOW);
Hope it helps
Dialogs are very simple widgets. Trapping focus is about the most complicated thing they do. It pains me that your issue has evolved into such a complex one.
Just to state the obvious, you do have complete control over positioning any individual dialog thanks to your configured class name.
.demo-dialog-example {
top: 20px;
left: 20px;
}
Also, in your showDialog method, why not set up a call-back via a promise for the open method? Something like:
this._mdPanel.open(config).then(function() {
var dialog = angular.element(document.querySelector('.demo-dialog-example'));
//Centering, positioning relative to target, or draggable logic goes here
});
I respect that you are trying to improve the logic of the plugin and do things the "Angular way", but these relatively simple requirements should not be causing you this much heartache.

Writing a directive to encapsulate multiple directives Angular

I'm using Angular 1.x and I have a section of code that I'm looking to repeat quite a bit, so I wanna throw it in a directive. The trouble is, it's somewhat complicated and I'm not sure how to begin writing it.
Essentially, it's a section of the page that displays various card directive and with infinite scrolling and perfect scrollbar.
<perfect-scrollbar refresh-on-change="myScope.data">
<div class="limit-columns">
<masonry masonry-options="{gutter: 30, isFitWidth: true}">
<user-card class="masonry-brick" ng-repeat="item in myScope.data"></user-card>
</masonry>
<div class="infinite-scroller" infinite-scroll="myScope.showMore()" infinite-scroll-tolerance="5"></div>
</div>
</perfect-scrollbar>
Perfect-scrollbar and masonry are both angular libraries on GitHub. Infinite-scroller is one I wrote myself, but works as you'd expect.
myScope contains a data attribute that is a list of objects containing a card's data. myScope.showMore is a function that adds items to that myScope.data list. Perfect-scrollbar also takes the refresh-on-change attribute which watches for changes on a particular object, in this case the list.
Ideally my directive would look something like this:
<card-scroller gutter="30" tolerance="5">
<some-card ng-repeat="achievements.data"></some-card>
</card-scroller>
But I'm not sure how feasible this is. Thanks!

Categories

Resources