With this tutorial i made simple drag and drop web app. But I cant do it with DOM. Here is my code jsfinddle . It is not working on jsfiddle but if u download it it will. The script should be placed behind the divs. When you uncoment <div class="column" draggable="true"><span>A</span></div> it will work (not in jsfiddle). So how can i made it with DOM ?
Going off the assumption you meant doing the draggable with dynamically created elements, I've updated your jsfiddle. http://jsfiddle.net/7c3v0s1s/6/ I wrapped the code in a namespace while doing the changes.
HTML
<div class="containter">
<div id="columns"></div>
</div>
Javascript
var localNameSpace = {
dragSrcEl: null
, bindDraggables: function() {
var cols = document.querySelectorAll('#columns .column');
[].forEach.call(cols, function(col) {
col.addEventListener('dragstart', localNameSpace.handleDragStart, false);
col.addEventListener('dragenter', localNameSpace.handleDragEnter, false);
col.addEventListener('dragover', localNameSpace.handleDragOver, false);
col.addEventListener('dragleave', localNameSpace.handleDragLeave, false);
col.addEventListener('drop', localNameSpace.handleDrop, false);
col.addEventListener('dragend', localNameSpace.handleDragEnd, false);
});
}
, createDraggables: function() {
var colDiv = document.getElementById('columns');
var divC = document.createElement('div');
var spanC = document.createElement('span');
divC.className = 'column';
divC.draggable = 'true';
spanC.innerHTML = 'A';
divC.appendChild(spanC);
colDiv.appendChild(divC);
}
, handleDrop: function(e) {
if(e.stopPropagation){
e.stopPropagation();
}
if(dragSrcEl != this){
localNameSpace.dragSrcEl.innerHTML = this.innerHTML;
this.innerHTML = e.dataTransfer.getData('text/html');
}
return false;
}
, handleDragEnd: function(e) {
var cols = document.querySelectorAll('#columns .column');
this.style.opacity = 1;
[].forEach.call(cols, function(col){
col.classList.remove('over');
});
}
, handleDragEnter: function(e) {
this.classList.add('over');
}
, handleDragLeave: function(e) {
this.classList.remove('over');
}
, handleDragOver: function(e) {
if(e.preventDefault){
e.preventDefault();
}
e.dataTransfer.dropEffect = 'move';
return false;
}
, handleDragStart: function(e) {
this.style.opacity = 0.4;
localNameSpace.dragSrcEl = this;
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', this.innerHTML);
}
, init: function() {
var readyStateCheckInterval = setInterval(function() {
if (document.readyState === "complete") {
clearInterval(readyStateCheckInterval);
localNameSpace.createDraggables();
localNameSpace.bindDraggables();
}
}, 10);
}
};
localNameSpace.init();
Related
The drag handler module does not work on Android.
It works on IOS and Windows.
Expecting to work on Android phones, just like on IOS or Windows.
How it should work on mobile: long tap to an element, drag the element over an other, release tap.
It works exactly like that on other OSes. Other module is calling the init() method every time it wants to reinit the drag handler.
Here is the module's code:
import * as devices from "./devices.js"
var items = [];
var dragSrcEl = null;
var init = function(){
items = document.querySelectorAll('.expander');
items.forEach(function (item) {
removeListeners(item);
addListeners(item);
});
}
var removeListeners = function (item) {
item.removeEventListener('dragstart', handleDragStart);
item.removeEventListener('dragend', handleDragEnd);
item.removeEventListener('dragover', handleDragOver);
item.removeEventListener('dragenter', handleDragEnter);
item.removeEventListener('dragleave', handleDragLeave);
item.removeEventListener('drop', handleDrop);
}
var addListeners = function (item) {
item.addEventListener('dragstart', handleDragStart);
item.addEventListener('dragend', handleDragEnd);
item.addEventListener('dragover', handleDragOver);
item.addEventListener('dragenter', handleDragEnter);
item.addEventListener('dragleave', handleDragLeave);
item.addEventListener('drop', handleDrop);
}
var handleDragStart = function(e) {
this.style.opacity = '0.4';
}
var handleDragEnd = function(e) {
this.style.opacity = '1';
}
var handleDragStart = function(e) {
this.style.opacity = '0.4';
dragSrcEl = this;
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', this.innerHTML);
}
var handleDragEnd = function(e) {
this.style.opacity = '1';
items.forEach(function (item) {
item.classList.remove('over');
});
}
var handleDragOver = function(e) {
e.preventDefault();
return false;
}
var handleDragEnter = function(e) {
this.classList.add('over');
}
var handleDragLeave = function(e) {
this.classList.remove('over');
}
var handleDrop = function(e) {
e.stopPropagation(); // stops the browser from redirecting.
if (dragSrcEl !== this) {
dragSrcEl.innerHTML = this.innerHTML;
this.innerHTML = e.dataTransfer.getData('text/html');
devices.reorderByDom();
}
return false;
}
export{init}
Tested on chrome,firefox and edge in Android.
I am performing drap and swap in javascript. I want to highlight when dragged element is placed on top of other draggable elements. For this I am using dragEnter and dragLeave events. But these are not working as expected. Here I have attached stakblitz link for the sample. Can anyone help me to fix this issue? I want a element to be in highlighted state when the dragged element on top of it.
In the above example I can drag A to the others(It can be B, C or D). when I dragged A and move to C(B or D) means C(B or D) should be highlighed.
Replace this with lastEle here:
function handleDragEnter(e) {
//lastEle ? this.classList.remove('over'): '';
lastEle ? lastEle.classList.remove('over'): '';
this.classList.add('over');
lastEle = this;
}
document.addEventListener('DOMContentLoaded', (event) => {
var dragSrcEl = null;
let lastElem;
function handleDragStart(e) {
this.style.opacity = '0.4';
dragSrcEl = this;
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', this.innerHTML);
}
function handleDragOver(e) {
if (e.preventDefault) {
e.preventDefault();
}
e.dataTransfer.dropEffect = 'move';
return false;
}
function handleDragEnter(e) {
lastElem ? lastElem.classList.remove('over') : '';
this.classList.add('over');
lastElem = this;
}
function handleDragLeave(e) {
// this.classList.remove('over');
}
function handleDrop(e) {
if (e.stopPropagation) {
e.stopPropagation(); // stops the browser from redirecting.
}
if (dragSrcEl != this) {
dragSrcEl.innerHTML = this.innerHTML;
this.innerHTML = e.dataTransfer.getData('text/html');
}
return false;
}
function handleDragEnd(e) {
this.style.opacity = '1';
items.forEach(function (item) {
item.classList.remove('over');
});
}
let items = document.querySelectorAll('.container .box');
items.forEach(function (item) {
item.addEventListener('dragstart', handleDragStart, false);
item.addEventListener('dragenter', handleDragEnter, false);
item.addEventListener('dragover', handleDragOver, false);
item.addEventListener('dragleave', handleDragLeave, false);
item.addEventListener('drop', handleDrop, false);
item.addEventListener('dragend', handleDragEnd, false);
});
});
Try this changes. It will work.
Is there a way to handle separated events by one condition, without checking it in every event handler function? I.e. in the code listed below. Or maybe there is a way to do it more convinient? Any thoughts/suggestions?
var Obj = function(){
var self = this;
this.initialized = true;
$(document).on('click', function(){
if(self.initialized){
alert('hax!');
self.initialized = false;
}
})
$('input').on('mouseenter mouseleave', function(e){
if(self.initialized){
$(this).attr('class', e.type == 'mouseenter' ? 'hovered' : '');
}
})
/*$('...').on(...) etc etc*/
}
var obj = new Obj()
.hovered{
color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type='button' value='Click me!'/>
i've played a bit around and maybe this is a working solution for you:
eventHandlerWrapper = function(state, callback) {
return function(e) {
if(state.initialized) {
return callback.bind(this, e)();
}
}
}
var Obj = function() {
this.initialized = true;
}
var obj = new Obj()
$(document).on('click', eventHandlerWrapper(obj, function(e){
obj.initialized = false;
}))
$('input').on('mouseenter mouseleave', eventHandlerWrapper(obj, function(e){
$(e.currentTarget).attr('class', e.type == 'mouseenter' ? 'hovered' : '');
}))
I've also create a fiddle: https://jsfiddle.net/jz17L7h6/
Can you add another class for your button?
var Obj = function(){
var self = this;
self.initialized = true;
self.dom = $('<input>').attr('type', 'button').val('Click me!')
.data('obj', this)
.appendTo($('.container'));
/*$('...').on(...) etc etc*/
}
$(document)
.on('click', 'input:not(.uninitialized)', function(){
alert('hax!');
$(this).addClass('uninitialized')
.data('obj').initialized = false;
})
.on('mouseenter mouseleave', 'input:not(.uninitialized)', function(e){
$(this).attr('class', e.type == 'mouseenter' ? 'hovered' : '');
});
for (var i = 0; i < 5; i++) {
new Obj();
}
https://jsfiddle.net/chukanov/fzez5th2/3/
If you can't add a class, you can turn off your listeners
var Obj = function(){
var self = this;
self.initialized = true;
self.dom = $('<input>').attr('type', 'button').val('Click me!')
.appendTo($('.container'));
var moveHandler = function(e){
$(this).attr('class', e.type == 'mouseenter' ? 'hovered' : '');
};
var clickHandler = function(){
alert('hax!');
self.initialized = false;
self.dom
.off('mouseenter mouseleave', moveHandler)
.off('click', clickHandler);
};
self.dom
.on('mouseenter mouseleave', moveHandler)
.on('click', clickHandler);
/*$('...').on(...) etc etc*/
}
for (var i = 0; i < 5; i++) {
new Obj();
}
https://jsfiddle.net/chukanov/fzez5th2/4/
Or you can override your handlers
var Obj = function(){
var self = this;
self.initialized = true;
self.dom = $('<input>').attr('type', 'button').val('Click me!')
.appendTo($('.container'));
self.moveHandler = function(e){
$(this).attr('class', e.type == 'mouseenter' ? 'hovered' : '');
};
self.clickHandler = function(e){
alert('hax!');
self.initialized = false;
self.clickHandler = function (e) {};
self.moveHandler = function (e) {};
};
self.dom
.on('mouseenter mouseleave', function (e) { self.moveHandler.apply(this, arguments); })
.on('click', function (e) { self.clickHandler(e); });
/*$('...').on(...) etc etc*/
}
for (var i = 0; i < 5; i++) {
new Obj();
}
https://jsfiddle.net/chukanov/fzez5th2/5/
Update
I think you need to separate mouse events handlers.
var Obj = function(){
var self = this;
self.initialized = true;
self.dom = $('<input>').attr('type', 'button').val('Click me!')
.appendTo($('.container'));
var handlers = {
mouseenter: function (e) {
$(this).addClass('hovered');
},
mouseleave: function (e) {
$(this).removeClass('hovered');
}
}
var moveHandler = function(e){
handlers[e.type].apply(this, arguments);
};
var clickHandler = function(e){
alert('hax!');
self.initialized = false;
clickHandler = function (e) {};
moveHandler = function (e) {};
};
self.dom
.on('mouseenter mouseleave', function (e) {
moveHandler.apply(this, arguments);
})
.on('click', function (e) {
clickHandler(e);
});
/*$('...').on(...) etc etc*/
}
for (var i = 0; i < 5; i++) {
new Obj();
}
https://jsfiddle.net/chukanov/fzez5th2/7/
on my website I have a div .toggle-search that if you click on it it expands to .search-expand where a search form is. This is the code in jQuery
/* Toggle header search
/* ------------------------------------ */
$('.toggle-search').click(function(){
$('.toggle-search').toggleClass('active');
$('.search-expand').fadeToggle(250);
setTimeout(function(){
$('.search-expand input').focus();
}, 300);
});
Now the only way to close the .search-expand is to click once again on the .toggle-search. But I want to change that it closes if you click anywhere else on the site. For an easier example I have the Hueman theme, and I'm talking about the top right corner search option. http://demo.alxmedia.se/hueman/
Thanks!
Add the event on all elements except the search area.
$('body *:not(".search-expand")').click(function(){
$('.toggle-search').removeClass('active');
$('.search-expand').fadeOut(250);
});
or another way,
$('body').click(function(e){
if(e.target.className.indexOf('search-expand') < 0){
$('.toggle-search').removeClass('active');
$('.search-expand').fadeOut(250);
}
});
var isSearchFieldOpen = false;
var $toggleSearch = $('.toggle-search');
var $searchExpand = $('.search-expand');
function toggleSearch() {
// Reverse state
isSearchFieldOpen = !isSearchFieldOpen;
$toggleSearch.toggleClass('active');
// You can use callback function instead of using setTimeout
$searchExpand.fadeToggle(250, function() {
if (isSearchFieldOpen) {
$searchExpand.find('input').focus();
}
});
}
$toggleSearch.on('click', function(e) {
e.stopPropagation();
toggleSearch();
});
$(document.body).on('click', function(e) {
if (isSearchFieldOpen) {
var target = e.target;
// Checking if user clicks outside .search-expand
if (!$searchExpand.is(target) && !$searchExpand.has(target).length) {
toggleSearch();
}
}
});
I have a second search on the site with the same code as before only
with div .toggle-serach2 and .expand-search2, how can i make your code
so it wont overlap. just changing the name to $('toggle-search2')
doesn't cut it
in that case, I would suggest you convert your code into a plugin:
(function($, document) {
var bodyHandlerAttached = false;
var openedForms = [];
var instances = {};
var defaults = {
activeClass: 'active'
};
function ToggleSearch(elem, options) {
this.options = $.extend({}, defaults, options);
this.$elem = $(elem);
this.$btn = $(options.toggleBtn);
this.isOpen = false;
this.id = generateId();
this.bindEvents();
instances[this.id] = this;
if (!bodyHandlerAttached) {
handleOutsideClick();
bodyHandlerAttached = true;
}
}
ToggleSearch.prototype = {
bindEvents: function() {
this.$btn.on('click', $.proxy(toggleHandler, this));
},
open: function() {
if (this.isOpen) { return; }
var _this = this;
this.$btn.addClass(this.options.activeClass);
this.$elem.fadeIn(250, function() {
_this.$elem.find('input').focus();
});
openedForms.push(this.id);
this.isOpen = true;
},
close: function(instantly) {
if (!this.isOpen) { return; }
this.$btn.removeClass(this.options.activeClass);
if (instantly) {
this.$elem.hide();
} else {
this.$elem.fadeOut(250);
}
openedForms.splice(openedForms.indexOf(this.id), 1);
this.isOpen = false;
},
toggle: function() {
if (this.isOpen) {
this.close();
} else {
this.open();
}
}
};
var toggleHandler = function(ev) {
ev.stopPropagation();
this.toggle();
};
var handleOutsideClick = function(e) {
$(document.body).on('click', function(e) {
if (openedForms.length) {
var target = e.target;
var instance;
for (var id in instances) {
instance = instances[id];
if (!instance.$elem.is(target) && !instance.$elem.has(target).length) {
instance.close(true);
}
}
}
});
};
function generateId() {
return Math.random().toString(36).substr(2, 8);
}
$.fn.toggleSearch = function(options) {
return this.each(function() {
if (!$.data(this, 'toggleSearch')) {
$.data(this, 'toggleSearch', new ToggleSearch(this, options));
}
});
};
})(window.jQuery, document);
And then use it like this:
$('.search-expand').toggleSearch({
toggleBtn: '.toggle-search'
});
$('.search-expand2').toggleSearch({
toggleBtn: '.toggle-search2'
});
JSFiddle example
You could add a click handler to the main window that removes the active class:
$(window).click(function(){
$('.toggle-search').removeClass('active');
}
and then prevent the class removal when you click inside of your toggle-search elem
$('.toggle-search').click(function(e){
e.stopPropagation();
// remainder of click code here
)};
Try to add body click listener
$('body').click(function(e){
if ($(e.target).is('.toggle-search')) return;
$('.toggle-search').removeClass('active');
$('.search-expand').fadeOut(250);
});
Please check it. Here is the Fiddle: http://jsfiddle.net/4467yz37/
When I do click in the Link it works good (Show and Hide). The only problem existing it's when I want to hide the Items section doing click outside the Link and the Items (that is in the Body except in the Items section).
Here is the JavaScript code:
(function(document) {
var alterNav = function() {
var item = document.querySelector('.items');
var link = document.querySelector('.clickme');
var theClass = 'display';
var itemIsOpened = false;
if (link) {
link.addEventListener('click', function (event) {
event.preventDefault();
if (!itemIsOpened) {
itemIsOpened = true;
addClass(item, theClass);
} else {
itemIsOpened = false;
removeClass(item, theClass);
}
});
}
};
var addClass = function (element, className) {
if (!element) {
return;
}
element.className = element.className.replace(/\s+$/gi, '') + ' ' + className;
};
var removeClass = function(element, className) {
if (!element) {
return;
}
element.className = element.className.replace(className, '');
};
alterNav();
})(document);
I try to solve it creating another variable with the tag Html or Body and alter the JS code, but it still don't working good: http://jsfiddle.net/g1d321rv/2/
var link = document.querySelector('body');
I manipulated your code a bit. Do you use jquery? I assumend that you are not using jquery.Here jsfiddle :
http://jsfiddle.net/9fpf07mt/
window.onclick = function (e) {
console.log(e);
if (!itemIsOpened) {
if (e.target == link) {
itemIsOpened = true;
addClass(item, theClass);
}
} else {
if (!isChild(e.target, item)) {
itemIsOpened = false;
removeClass(item, theClass);
}
}
};
edit for last request:
link.addEventListener('click', function (event) {
event.preventDefault();
});