Drag Drop CDK: keep showing dragged element inside starting list - javascript

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 });
}

Related

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.

Click event not registering on div element (jQuery/HTML/CSS)

This is my game: (i can't post images yet so I have to explain with words)
This is a Connect 4 game.
Imagine 7 div columns.
The 7 columns have 6 div chip objects stacked in each column element.
(7x6 grid with 42 chips)
I put each chip (each black circle) as a div object prepended into each column container.
that is... each .columncontainer div has nested under it a bunch of .chip div elements. (they're the chips/circles)
What I want:
I want to be able to click a specific chip object (they're div's.. the black circles) and I want ONLY that one to turn full black. (default opacity I set to 0.5)
This is the code I have:
$(document).ready(function colorSelectionListener(){
$(".columncontainer").children().click(function() {
window.alert("clicked!");
$(this).css("opacity",1);
});
What's actually happening: When I click on ANY of the chip objects... NOTHING happens. I can't click the chips at all.
What I can do: To test if I can click something I made the alert "clicked!" In this way I am able to click the columncontainers. I am also able to retrieve the index of the column div inside it when I print out the index of 'this', like so:
window.alert($(this).index());
instead of the 'clicked!' message. It gives me 6...which doesn't make sense... because 6 is the last element inside columncontainer which is .column. (0-5 elements must be the chips after I prepend them right?)
What I tried: I tried making the .click with the chip objects themselves. (the class attached to every chip object is '.chip') Did not work. (Click was not registering... but I think that's another problem)
Can someone enlighten me?
EDIT 1:
Mini Recreation of Problem
https://jsfiddle.net/9z916z2u/65/
If anyone could help me I would really really appreciate it! I'm having so much fun coding this right now but this is annoying :/ I learnt jQuery/Javascript around 3 days ago, so I'm not that good. (I have coded in Java/Python before though)
Try this:
$('.columncontainer .cheap').click(function(){
$(this).css('opacity', 1)
})
Or, if you want to add .cheap blocks dynamically, you can use this variant:
$('.columncontainer').on('click', '.cheap', function(){
$(this).css('opacity', 1)
})
$(document).ready(function(){
$(".columncontainer > childElement").click(function() {
window.alert("clicked!");
$(this).css("opacity",1);
}
});
replace child element with d tag or the class of the childred of .columncontainer
Posted code in JSFiddle needs some adjustments to work. I've modified the event subscription to match late binding (according to Anatoliy Arkhipov).
$('.columncontainer').on('click', '.cheap', function(){
Instead of
$('.cheap').on('click', function(){
And removed position: relative for the .column class. Here is how the code looks now and it works in Chrome and FF.
However, I consider the answer to be not consistent (why does position: relative prevented binding?) and maybe masters of css can explain that.

Workaround for :empty Selector Not Being Updated When Children Added in IE11

I have simple css statement I want to apply to some HTML that basically only shows an element when its previous sibling is not empty. The use case is to only show a "Clear" button when a list has items in it.
<ul></ul>
<button>Clear</button>
ul:empty + button
{
display: none;
}
The problem is that whenever my javascript inserts a new list item into the list, Chrome appears to work correctly and automatically make the button visible. IE11 however fails to show the Clear button. This JS fiddle illustrates the problem: http://jsfiddle.net/xw4nbnz3/
What is the easiest workaround for this problem to make ie11 work? Preferably staying in CSS only.
Pretty sure I'd seen this before, but I can't find the previous question anymore so I'll take a stab at it.
This looks like a typical repaint bug: IE will understand your CSS just fine... if the list starts out with items, and if you write a function to empty the list instead, it will not update the button either. Unfortunately, repaint bugs aren't known for having pure CSS workarounds.
Fortunately, they don't usually need them. Since this only happens when you change the DOM, you can easily work around this within the JS, and it doesn't take very many lines. It looks like removing the list itself first, before inserting the new item, then putting the list back in resolves this issue whilst not creating any problems in other browsers:
function AddListItem()
{
var mylist = document.getElementById("mylist");
if (!mylist)
return;
var parent = mylist.parentElement;
var button = mylist.nextElementSibling;
parent.removeChild(mylist);
var li = document.createElement("li");
li.appendChild(document.createTextNode("Hello, world!"));
mylist.appendChild(li);
parent.insertBefore(mylist, button);
}
Tested on IE11 on Windows 7, Windows 8.1, and Windows 10 RTM (build 10240), and Microsoft Edge on Windows 10 RTM. Note that removing the list after inserting the item (right before putting it back in) does not work; you will need to remove it first.
The issue is related to cascaded selectors like the button in the selector ul:empty + button.
It's safe to use the :empty pseudo-class in IE11 when it applies to DOM element itself https://codepen.io/egeneralov/pen/geRQXz.

Populating icons in Sencha Touch selectfield

I've got a pretty simple problem whose solution turns out not to be that simple at all.
I want to add images in front of each option of a selectfield. To be more accurate, I want to add images to the picker it triggers, and also to the selectfield's current value.
For the sake of simplicity, I'll create a little example:
Let's say, you want a user to choose between one of the four playing card suits Diamonds, Hearts, Spades and Clubs. To support visual recognition, you want to prepend the corresponding symbol to each suit name, so it could look something like this:
My first choice of a sencha touch component, that enables selecting from a given set of options naturally was selectfield. Unfortunately, it only seems to be able to display pure text, and nothing more. After digging into the sencha touch sources, I finally came up with half a solution. Basically, I pass the selectfield a custom defaultPhonePickerConfig, in which the corresponding picker(that is used by the selectfield to display the options) gets assigned a custom itemTpl. The itemTpl does the rest, namely adding some html to display the image:
defaultPhonePickerConfig: {
listeners: {
initialize: function() {
var slots = this.query('pickerslot');
Ext.each(slots, function(slot) {
slot.setItemTpl('<img src="someImage.jpg"> {text}');
});
},
change: function() {
// reconstruct the selectfield's change handler,
// since it gets overwritten
var iconSelect = Ext.Viewport.query('#IconSelect')[0];
iconSelect.onPickerChange.apply(iconSelect, arguments);
}
}
}
A working fiddle for this solution can be found here.
My solution isn't that bad, but there's a slight cosmetical problem, that's just not acceptable to me: The icons are only displayed in the picker (lower part of the screenshot above), but not the selectfield itself (upper, grayed out part) when the option was selected. And there seems to be no good way to add an icon to the selectfield's current value aswell.
And that's the main concern of my question: What good way is there to add an icon to both the picker's options and also to the selecfield's current value? Do I maybe just have to add relatively little code to my existing solution or should I take a whole nother approach?
Every contribution is appreciated. Thank you!
Update:
After some hacking around, I found a way (an ugly one) to prepend an icon to the selectfield itself. It is mostly based on brutal HTML DOM manipulation: I now also define event handlers for the selectfield itself (change and painted). On initialization and every time the value is changed, my handlers search the generated DOM for the underlying <input> and mess around with it. (That bad boy is probably the reason why we can't assign HTML in the first place, since the framework changes its value attribute. And value on the other hand can only contain plain text.)
This is how I define the selectfield's listeners:
listeners: {
change: function () {
var pickerDOM = document.querySelector('#' + this.getId() + ' input[name="picker"]');
PickerIcons.app.prependIconToSelectfield(arguments[1], pickerDOM);
},
painted: function () {
// Initialize an icon on creation
var pickerDOM = document.querySelector('#' + this.getId() + ' input[name="picker"]');
PickerIcons.app.prependIconToSelectfield(this.getValue(), pickerDOM);
}
}
The corresponding function prependIconToSelectfield() just defines some CSS:
prependIconToSelectfield: function (optValue, domElement) {
var iconUrl = this.getIconUrl(optValue);
domElement.style.backgroundImage = 'url(' + iconUrl + ')';
domElement.style.backgroundSize = '20px 20px';
domElement.style.backgroundRepeat = 'no-repeat';
domElement.style.backgroundPosition = 'left center';
domElement.style.paddingLeft = '30px';
}
Check out this fiddle for a working example.
This is still no good solution to me, since doing this kind of hackish DOM manipulation is way too rogue for my taste. I don't know what the side effects of actively messing around in the DOM could be in bigger projects, and don't want to learn it the hard way. So, I'm still looking for a cleaner solution.
First kudos on working so hard sencha touch is extremely hard to manipulate when you try to do something out of the box. Having said that let me try & propose a solution for what you want.
A selectfield in sencha has the following DOM tag structure.
div.x-field-select
div.x-field-input
input.x-input-el
div.x-clear-icon
div.x-field-mask
Now concentrate on the x-clear-icon it is normally hidden since a selectfield does not need a clear button. First write a css class for it to show it(display: block). This would display it with an X button similar to text field & it will be positioned towards the right corner. You can through css position it to the left and on change of the select field you can change its background to what you want. It is not a very straight forward solution but i have tried it for a similar problem & it works. Judging from what you have done above i think you can do it. All the best.
Hope my solution helps.

jQuery - select invisible text overlaid on image, ala GMail PDF viewer

I'm overlaying invisible text on top of an image. Is there a jQuery plugin (or similar) that will allow users to select an area on the image (which also selects the overlaid text) and be able to copy the content.
Right now, I have placed each character in its own <span /> tag. Problem is when user selects, it sometimes select all the overlaid text (unless user is extremely precise with his/her mouse), sometimes the image itself becomes selected, etc.
A solution similar to GMail's PDF viewer would be nice. Suggestions?
Google seems to know from a pdf where the various x,y text offsets are in the file. When you select a bunch of lines, it places a set of absolutely positioned "selection" divs over the image where the "text" is (they have class highlight-pane). When you select text, it fills in a #selection-content textarea with the contents of what you have selected, and selects the text in it (try using window.getSelection().anchorNode in Chrome, e.g.). Besides those selection overlays, there is just an image .page-image. I bet they actually use window to capture all the mouse gestures they care about (I assume mousedown and mouseup). (Here's an example pdf document)
If you're absolute-positioning the elements, you could detect mousedown, mousemove and mouseup, figure out the span elements mouse is over (or nearest to), and fill in a textarea with the contents of all content between those two elements. If you want to just use word-granularity, I doubt anyone would complain (surround each word with a span, rather than each letter).
Edit: I got kinda curious last night and coded up a really naive version. It only does mousedown and mouseup, and it doesn't work in IE (I don't feel like debugging it :)
Check it out on jsfiddle.
Features you might want to add:
Some better way of checking for position-based matches; I just do whether it's included in the box.
Dynamic updating on mousemove
Line-based rather than span-based
You could still do selecting by background color, but depending on how your elements are arranged it might not look very good. Also would need to support transparency.
Here's a simple example using my answer to your previous question: http://www.jsfiddle.net/yijiang/83W7X/2/embedded/result
var selected = [];
function drawSelection(){
if(selected.length){
selected.sort(function(a, b){
if(a.sourceIndex){
return a.sourceIndex - b.sourceIndex;
} else if(a.compareDocumentPosition){
if(a.compareDocumentPosition(b) == Node.DOCUMENT_POSITION_PRECEDING){
return 1;
} else {
return -1;
}
}
});
var range = rangy.createRange(),
sel = rangy.getSelection();
range.setStart(selected[0].children[0], 0);
range.setEnd(selected[selected.length - 1].children[0], 1);
sel.setSingleRange(range);
}
}
$('ul').selectable({
delay: 100,
selecting: function(event, ui) {
if(ui.selecting.getAttribute('class').indexOf('wrapper') !== -1 && $.inArray(ui.selecting, selected) === -1) {
selected.push(ui.selecting);
drawSelection();
}
},
unselecting: function(event, ui){
if(ui.unselecting.getAttribute('class').indexOf('wrapper') !== -1 && $.inArray(ui.unselecting, selected) > -1){
selected.splice($.inArray(ui.unselecting, selected), 1);
drawSelection();
}
}
});
It mixes jQuery UI's Selectable with Tim Down's excellent Rangy library to create something similar to what you asked for. I think. What you asked for wasn't exactly clear.
The code keeps an array of currently selected li elements. The second part of the code adds in the relevant event handlers and options. The drawSelection function is called every time an element is selected or deselected. The function first sorts all elements by their position in the DOM, then proceeds to draw a selection from the first selected li to the last.
The code, like theazureshadow's, is proof-of-concept only, since I'm abstracting what really should be the simple task of selecting the lis to the rather heavy Rangy library. It also does not perform very well and could do with some refactoring.

Categories

Resources