dragover event, target needs to be element with draggable - javascript

I need to get the parent element which has got the draggable attribute assigned to it, as this also has some other data attributes I need.
However if I click and drag on a child element of course this is the target.
Is there a way to do this without a horrid findAncestor loop?
<div class="panel" draggable="true" data-move-id="1">
<div data-move-handle class="panel-heading"></div>
<div class="panel-block"></div>
<div class="panel-block"></div>
<div class="panel-block"></div>
<div class="panel-block"></div>
</div>
Or, better yet, is there a way I can limit the draggable handle to the div with the data-move-handle attribute, however I still want the entire div to move. Then I can move the data-move-id to that div.

To have data available when you drop, from your 'dragged' element, you have to include it in your event. This can get tricky with cross browser implementations, especially if you have to support legacy (i.e. IE) browsers.
const startDrag(event) {
// 'this' is the 'draggable' you are attaching this method to,
// so if you need the data attribute just use this.getAttribute()
// get it.
// get the data you need to transfer with your object, and then
event.dataTransfer.setData('text', JSON.stringify(somesetofdata));
}
const drop(event) {
let data = event.dataTransfer.getData('text');
data = data ? JSON.parse(data) : data;
// do something with it
}
That's sorta the gist. Google 'Pure Javascript Drag and Drop' for a lot of example code. Here's a really simple example.

Related

Get vue component from event

I am building a drag and drop feature in my application where the drop area looks like this:
<template>
<div class="layers-panel" #dragover="onDragOver" #drop="onDragDrop">
<layer
v-for="(layer, index) in layers"
:key="layer.name"
:info="layer"
:offset="index"
ref="layer"
></layer>
</div>
</template>
Then the draggable items look like this:
<template>
<div class="layer" draggable="true" #dragstart="onDragStart">
<!-- Content of draggable element -->
</div>
</template>
In my drag over event, I want to get the current component that the element relates to, so when I drag an item over the <div class="layer"...> element I would like to get that component. I am not sure how to get access to this since an event is passed to the #dragover="onDragOver" event which only gives me access to html elements and not Vue components.
This is what my current dragover event looks like:
public onDragOver(evt: DragEvent) {
evt.preventDefault()
if (!evt.target) return
let layer = (evt.target as HTMLElement).closest('.layer') as HTMLElement
}
How can I get the component that that element belongs to?
Vue certainly allows you to access a component instance however I don't think it's necessary to reaching your actual goal: reordering of components via drag-and-drop.
Drag-and-drop means we're limited by what's afforded by DragEvent:
We can access the target element e.g. the element being dragged or dropped on
We can set arbitrary data via DataTransfer
You can't determine where the element is dropped because your drop element contains all the layers. Instead if each layer is a drop element, then you can determine which one it is via dragover via event.target.
We also need to identify which element corresponding to the layers array we're dragging/dropping on. This can be achieved by binding the index of each layer as an attribute so we can access it via event.target.
To keep it simple, I'll use the id attribute so I can just access it later via event.target.id:
<div
v-for="(layer, idx) in layers"
:key="idx"
:id="idx"
draggable="true"
#dragstart="dragstart"
>
<div
:id="idx"
class="draggable"
#dragover="dragover"
#drop="drop"
>
{{ layer }}
</div>
</div>
Note I bind the same index to id so I can correctly identify the target layer.
We'll also need to ensure we can identify the element that was dragged since on drop we'll only have the drop element via event.target. We can do this by setting data in DataTransfer when we first start the drag:
dragstart(event) {
event.dataTransfer.setData("selectedIdx", event.target.id);
},
Now on drop we have access to both and can manipulate layers as we desire:
drop(event) {
var selectedIdx = event.dataTransfer.getData("selectedIdx");
var targetIdx = event.target.id;
var selected = this.layers.splice(selectedIdx, 1)[0];
this.layers.splice(targetIdx, 0, selected);
},
Example codepen

Move Element to another div without losing events, listeners, etc (without jQuery)

Simply put, I'm trying to move a submenu from one location to another. (Easy)
I need to do this without losing any event handlers, etc. (Harder)
<ul id="Menu A">
<li id="submenu">My Submenu with Clicky Events</li>
</ul>
<ul id="Menu B">
<!--This is where I want to move #submenu (with) all its' events intact -->
</ul>
I've tried the following without success:
var submenu = $('#submenu').clone(true,true);
submenu.prependTo('.Menu B');
Ideally, I can find a solution without using jQuery, but if you have a solution that works with it, that would be okay.
With jQuery you can use .appendTo()
$('#element_to_move').appendTo('#PARENT_at_destination');
jQuery will cut it from its present location and move it to the new location, without losing bindings etc. However, styles could be affected because the DOM location will change.
This could also be helpful: https://api.jquery.com/detach/
With JavaScript you don't have to clone() the node you want to move, just get a reference to it and then insert it wherever you want, for example:
// simple function to demonstrate a click-handling:
function spanClick (){
// message to demonstrate that the function still works
// without having to worry about cloning:
console.log("So, you've clicked the span.");
// moving the element elsewhere:
div.appendChild(this);
}
// references to the various elements:
var span = document.getElementById('demo'),
div = document.getElementById('recipient');
// assigning the click event-handler:
span.addEventListener('click', spanClick);
function spanClick() {
console.log("So, you've clicked the span.");
div.appendChild(this);
}
var span = document.getElementById('demo'),
div = document.getElementById('recipient');
span.addEventListener('click', spanClick);
span {
color: #f90;
}
div {
border: 1px solid #000;
margin: 1em;
}
div span {
color: #0f0;
}
<p>If you click the following <code>span</code> element, you'll log a message to the console (<kbd>F12</kbd>), and move it to inside the following <code>div</code> element. <em>It will still trigger the message</em>.</p>
<span id="demo">This is the <code>span</code></span>
<div id="recipient"></div>
cloneNode(), or clone(), is only required if you need to, for whatever reason, duplicate the node.
References:
EventTarget.addEventListener().
Node.appendChild().
I realize this thread is super old but if anyone is here and wants to move an element WITHOUT jQuery, you can simply use insertBefore and this will move the element with all of its children and retain all event listeners, etc.
For example:
let el = document.getElementById("thingBeingMoved");
let moveTo = document.getElementById("otherThing");
moveTo.parentNode.insertBefore(el, moveTo);
moveTo is the element in the document that will be just after or next to the element being moved. If you want to move to the very end you can just get the parent container and use appendChild.
I am using this to reorder rows that contain many child elements including charts that are created with an external library. Even if the charts have not finished loading/rendering while I am doing the move, it does not cause any issues because the container and all of it's children that are being moved is not destroyed or altered in any way. In fact, I can still see the charts being loaded into the container as I am moving it using some pretty basic drag and drop event handlers and setting the elements position like this:
element.style.position = "fixed";
element.style.left= pageX + "px";
element.style.top = pageY + "px";
element.style.zIndex = 99;
Have you tried it with either:
- cloneNode(true|false) and removeChild() or
- replaceChild()
yet?
EDIT:
Here the example you asked for: I'll move the yahoo's menubar on the left hand side to the center. The moved menubar will have all the functionalities that it has before. (I hope that's the example you're expecting)
To make it more clear I'll use the Chrome developer tools. (the code works the same as in Firebug with Firefox)
Use the browser Google Chrome and go to https://de.yahoo.com/?p=us
[ctrl.]+[shift]+[j] in order to open developer tools.
[esc] in order to open the console in the dev tools.
type in the console the following things:
var myNode=document.querySelector("#nav-col-holder");// the element you want to move
var newNode=myNode.cloneNode(true);//it's complete clone with all the children node
var insertLocation=document.querySelector("#yui_3_8_1_1_1413153166615_364");//the location you want the clone to move to
Analyse where exactly you want the clone to remove to. Use dir(insertLocation) to get the exact target position. In this ex. I'll just put it before the 6th childNode of insertLocation
insertLocation.insertBefore(newNode, insertLocation.childNodes[5]);//the clone node is at the target position
myNode.parentNode.removeChild(myNode);//remove the original node
Now, you should see that I have just moved the menubar at the left hand side to the center of the page without loosing any functionalities of this menubar.
Hope I could help out. Bye.
You are using id twice. Try this:
<ul id="menua">
<li id="submenua">My Submenu with Clicky Events</li>
</ul>
<ul id="menub">
<li id="submenub">My Submenu missing Clicky Events</li>
</ul>
And .js:
$( "#submenua" ).clone().prependTo( "#submenub" );
// If you want to move instead of clone, add this line:
$("#menua #submenua").remove();

How do I use jQuery to hide an element with no class or ID... when the parent has no id either?

I want to use jQuery to work with events in a given search box. My issue is that
I don't know how to build the selector correctly, so that JQuery accepts it.
I think I'm getting confused because I need the second element in the list and need to select that one.
The runtime HTML looks like this: (Adapted from Chrome Developer tools, only the relevant class and IDs are shown. There are no IDs to be shown.)
<body class=km-ios7 km-7 km-m0 km-web km-black-status-bar km-vertical km-widget km-pane>
<div class="km-widget km-view">
<!-- Begin 3rd party control -->
<div class=class="km-widget km-view">
<div km-header>
<div class="km-content km-widget km-scroll-wrapper">
<div class=km-scroll-header>
<div class=km-scroll-container>
<div class="km-listview-wrapper">
<form class="km-filter-form">
<div class="km-filter-wrap">
<input type=search >
What I've tried
Since my event wasn't firing I assume my selector was wrong. I opened chrome developer tools after I did "inspect element". The bottom of the tools listed all the parent tags used for that element (with no class or ID). As a test, I've tried hiding the search box using the following:
$("div").hide(); // hides everything...
$("div div").hide(); // hides the wrong element on the page
$("input").hide(); // nothing
$(":input").hide(); // nothing... saw this example somewhere, don't understand it
$("input:text").hide(); // nothing... saw this example (http://stackoverflow.com/q/17384218/328397), don't understand it
I looked at this W3 document, but didn't see what I was looking for (unless I missed it)
Any assistance in getting the right selector would be appreciated.
In the page you linked it's the second div under defaultHomecontent, so
$("#defaultHomeContent div:nth-child(2)")
You actually want to hide the div with class km-filter-wrap.
A safer alternative may be to not deal with selectors and instead show/hide the wrapper element for the ListViewFilter's searchInput element:
var listView = $("#local-filterable-listview").kendoMobileListView({
...
}).getKendoMobileListView();
listView._filter.searchInput.parent().hide();
or
listView.wrapper.find(".km-filter-wrap").hide();
In general, it's a good idea to use the elements that are exposed by Kendo UI controls as much as possible instead of manually building queries (since they might change in future versions).
You could also extend the ListView widget with your own API method for this:
kendo.mobile.ui.ListView.fn.filterVisible = function(value) {
var wrapper = this._filter.searchInput.parent();
if (value) {
wrapper.show();
} else {
wrapper.hide();
}
};
then you could use
listView.filterVisible(false); // hide the filter
you can use the find function. Let suppose you have input field inside footer div like this.
<div id="footer">
<div>
<div>
<input type="text" name="text" value="Search" />
</div>
</div>
</div>
You can use selector like this $("#footer input").hide() or $("#footer").find("input").hide() or $('input[name=text]', '#footer').hide();
Based on what you have added.
You could use
$("input[type='search']")
as a selector.
See if that helps. Here is an example
You could also combine the selectors in this manner:
var $container = $("div.km-widget");
var $searchBox = $container.find("input[type='search']");

How can I select an element based on its position?

How is it possible to select an element based on its position?
<div id="parent">
<p id="text">This is a text</p>
</div>
//CSS
#parent{width:100px;height:100px;position:absolute;left:100px;top:100px;}
Now using the left=100px,top=100px how can I select the div element using JQuery?
Something like this might work:
var el = document.elementFromPoint(100, 100);
var $el = $(el); // if you really want to use jQuery.
Documentation for document.elementFromPoint.
This might not be what you're looking for: it will only get the visible, topmost element at that point, and it will get whatever element occupies that space, not just one whose top-left corner is at that point. But, chances are, it'll help :).
It is supported by all major browsers, although you could get wacky results with Safari 4 or Opera 10.10.
The jQuery css() function allows you to retrieve the value of CSS properties, for example css('left') will return the left position of your element.
However, in your question you as about selecting the div element based on its location. I presume you mean something like $('left=100px'). This is not possible. jQuery uses CSS selectors which query the structure, not the style of the DOM.
You could loop through every element to check the position. Here is a quick sample function:
function getElementsByPosition(x,y) {
var elements=new Array();
$('*').each(function() {
if($(this).css('top')==x && $(this.css('left'))==y) {
elements[elements.length]=this;
}
});
return elements;
}
Update: Domenic's solution is much more elegant. Please consider his before using mine.

jQuery .focus doesn’t actually focus a newly created element

Let’s say I have this markup:
<div id="content">
<div id="firstP"><p>First paragraph</p></div>
<div id="secondP"><p>Second paragraph</p></div>
<div id="thirdP"><p>Third paragraph</p></div>
<div id="fourthP"><p>Fourth paragraph</p></div>
</div>
I want to add a new <div> with jQuery and focus on this new element. .focus does not do anything.
function addParagraph() {
var html = "<div id=\"newP\"><p>New paragraph</p></div>";
$("#content").append(html);
$("#newP").focus();
}
Any idea why?
I think the main answer is incorrect. DIV and P tags can receive focus providing you specify a tabindex property for them. i.e.
<div class="someclass" tabindex="100">
Once the tabindex is specified you can either tab to these elements or shift focus with .focus() .
Using a scrollTo plugin seems like a bit of an overkill here.
There's no problem with your code, it's just that a paragraph or div tag can't receive focus. Focus can only be given to things you can interact with, such as links, input elements, textareas, etc.
To scroll the window to this newly added element, you can use a plugin such as ScrollTo.
On a side note, your code could be simplified a bit:
var html = "<div id=\"newP\"><p>New paragraph</p></div>";
$("#content").append(html);
$("#newP p").focus();
var html = "<div id=\"newP\"><p>New paragraph</p></div>";
$(html)
.appendTo('#content')
.focus() // or scrollTo(), now...
;
This code will avoid dependencies on other plugins and allow you to have it on any element.
$('html, body').animate({ scrollTop: $("#newP").offset().top }, 500);
You need to use a HTML page anchor not focus. Example:
http://localhost/mypage.html#fourthP
I think what you're looking for is using the 'ScrollTo' plugin in jQuery. You can check it out here.
You can specify what to scroll...
$('div.pane').scrollTo(...);//all divs w/class pane
Or just scroll the window:
$.scrollTo(...);//the plugin will take care of this
There are many different ways to specify the target position.
These are:
A raw number
A string('44', '100px', '+=30px',
etc )
A DOM element (logically, child of
the scrollable element)
A selector, that will be relative to
the scrollable element
A hash { top:x, left:y }, x and y
can be any kind of number/string
like above.
Bonus:
In digging up this information, I also found LocalScroll and SerialScroll (animates scrolling from one item to the next).
Only form elements and such can attain focus. If you want the browser to scroll down to that particular paragraph, there is no "default" way of doing so with jQuery, but there is a plugin for it at jQuery.ScrollTo and a blog explaining how to do it manually at Animated scroll with jQuery
Instead of
$("#newP").focus();
it should be used:
window.location.href=window.location.href + "#newP";
You can assign tabidex and then you can set focus to that element
$('#table').attr("tabindex",1).focus();
This will work for Div Table etc which can't get focus by itself.

Categories

Resources