I am trying to drag live divs returned from ajax. the following code works nice for desktop, but on iPad, I have to drag (on page load) each of the draggable twice, once to initialize and then to drag. Any help in making this one drag even on page load? My code is as the following:
html:
<div class="draggable"> drag this </div>
jQuery:
$.fn.liveDraggable = function (opts)
{
if((navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i)) || (navigator.userAgent.match(/iPad/i)))
{
this.live("touchstart", function() {
if (!$(this).data("init")) {
$(this).data("init", true).draggable(opts);
}
});
return $();
}
else
{
this.live("mouseover", function() {
if (!$(this).data("init")) {
$(this).data("init", true).draggable(opts);
}
});
return $();
}
};
$(".draggable").liveDraggable(
{
helper: 'clone',
containment: '#origin'
})
You can't bind mouseover events and expect them to have a 1 to 1 relationship on touch devices. Simply put I have the same issue and have not found a solution besides not using a live model which kind of sucks, as you have to re-initialise everything everytime you add a new dom element meant to inherit those properties.
On a side note.
try the following for determine if you have already attached a live event to that element.
if (!$(this).data("draggable")) {
$(this).draggable(opts);
}
Basically you are negating the need of adding random data() attributes like
'.data("init", true)'
The above prevents cluttering that name space with something you don't need, since draggable once attached is always true, and draggable methods can be removed quite easily.
Related
I'm trying to make a CKEditor widget with children that listen to the dragstart drag and dragend events. The issue is that these events never get fired - it's like the .cke_widget_wrapper (the div that wraps widgets) cancels those events.
Note that I'm not trying to make the entire widget draggable (part of the widget functionality), but make elements within the widget draggable.
If I unwrap the children of .cke_widget_wrapper (thereby removing the wrapper) then everything works as expected. But it seems this wrapper stops children from dragging.
I won't post the code on how I'm doing the dragging because it works as expected in an isolated test case, and as explained, works when I unwrap the widget from .cke_widget_wrapper.
Here's how I'm creating the widget:
CKEDITOR.plugins.add('embed', {
init: function(editor) {
editor.widgets.add('gallery', {
init: function() {
// code here that generates the HTML
},
upcast: function(element) {
// Defines which elements will become widgets.
if (element.hasClass('gallery')) {
return true;
}
},
downcast: function(element) {
element.setHtml("");
},
draggable: false, // This does not make a difference, but I set it to false because I don't want to use the built in widget dragging functionality
template: "<div class="gallery" trix-id='{id}'></div>",
defaults: function() {
return {id: 1} // As an example
}
});
}
});
Here's the generated HTML:
<div tabindex="-1" contenteditable="false" data-cke-widget-wrapper="1" data-cke-filter="off" class="cke_widget_wrapper cke_widget_block cke_widget_selected" data-cke-display-name="gallery" data-cke-widget-id="0">
<div class="gallery cke_widget_element" trix-id="1" data-cke-widget-upcasted="1" data-cke-widget-keep-attr="0" data-widget="gallery">
<!-- Other HTML -->
<div class="resize" draggable="true" trix-gallery-resize="1"></div>
</div>
</div>
The div.resize element has event handlers attached to it to allow dragging. As mentioned, this works in an isolated test case, and when I remove the .cke_widget_wrapper wrapper.
Does anyone know how I can allow dragging within the widget, so that the event handlers behave normally on the .resize element?
Thanks
As usual, as soon as I post a question to SO I work out the answer.
CKEditor attaches a mousedown event to the editable area which does many things, but it seems one of the side effects is it stops widget contents from being draggable.
All I had to do was attach an mousedown event handler to the .resize element which calls event.stopPropagation, like so:
element.on('mousedown', function(e) {
e.stopPropagation();
});
Easy!
I'm still a novice, so please go easy on me!
I'm making a JavaScript game. The game works fine, as do the basics of the user interface, like making menu selections or switching screens. But I'm also trying to implement jQuery UI sliders in one of my options menus, which is where I run into trouble.
I can only use the slider once, after which it becomes "stuck." It responds to mouseover - it'll highlight as though it's ready to scroll - but will not budge if I try to move it again.
So far, I've ruled out any problems with the build of jQuery/jQUI I'm using; the demo page works fine.
I have no idea what the problem might be, but I suspect it has something to do with the way I've put together my UI. The way my UI works is by creating a "View" object that contains pointers to a parent DOM element. I then use jQuery to construct its children and use the "loadElement" method to add it to the view's list of children elements:
function CView (parent, target, visible, jQElements) {
this.parent = parent;
this.visible = visible;
this.parentDisplay = parent.css("display");
this.parentPosition = parent.css("position");
this.elements = [];
for(element in jQElements) {
this.elements.push(element);
}
if (!this.visible) {
this.parent.css({ // Default to hidden state
"opacity": 0,
"display": "none"
});
}
this.parent.appendTo(target);
};
CView.prototype.loadElement = function(element) {
element.appendTo(this.parent);
this.elements.push(element);
return element;
};
All these elements can be shown and hidden together with a method call on the View object. Currently, hiding a view unbinds all event listeners in the elements of that view. I don't think this is the problem, since I get this problem immediately after creating a new view.
The issue, I think, might be in this code, which is for swapping views- Perhaps I'm unbinding some kind of document-level listener that jQUI uses?
var swapView = GameUI.swapView = function(view, callbacks) {
$(document).off(); // unbind key listeners
currentView && currentView.hideView(); // also unbinds event listeners
currentView = view;
view.showView(callbacks);
};
There's one more thing that might be relevant, the way I construct the slider and put it in:
var $volumeSlider = jQuery("<div/>", {
class: "options-menu-volume-slider"
});
var resetVolumeSlider = function () {
$volumeSlider.slider({
range: "min",
value: GameUI.options.volume,
min: 0,
max: 100
})
};
resetVolumeSlider();
If you need to see more code, let me know. I really am not sure what's going wrong here. Any and all help is appreciated. (Also, I don't know how to host my game online to demo it. It's basically just an HTML page that runs a bunch of JS.)
It turns out that this problem was caused by my call to $(document).off(), which I used to remove potentially dangling document-level keypress handlers. This had the unfortunate result of also destroying event handlers for jQuery UI.
In the future, my views will have keypresses bound at the parent div level with tab indices set for each div, so that I don't have to make the call to $(document).off() and can simply use hideView() to unbind.
I have a DOM element that has YUI event listeners on elements inside of it. I'm trying to get the DOM element to go on a different part of the page, but once I do that, I seem to lose the events that were set in YUI. Does anyone know of a way around this?
I'm facing exactly the same issue. I have both YUI and jQuery in the same app because it is a previously existing app originally built with YUI and I'm trying to use jQuery for implementing a functionality which is easier to achieve with jQuery.
As I have experienced, you shouldn't lose YUI events just by moving the element:
jQuery("#element1").before(jQuery("#element2"));
On the other hand, it will be a problem if you try to clone and swap elements, even using the clone function with arguments for cloning events as well:
var copy_to = $(to).clone(true);
var copy_from = $(this).clone(true);
$(to).replaceWith(copy_from);
$(this).replaceWith(copy_to);
According to that, I would say it is not possible to CLONE elements preserving events bound using YUI, but it is possible to MOVE them.
EDIT:
This is how I'm moving and swapping two DOM elements which have YUI events bound:
function makeElementAsDragAndDrop(elem) {
$(elem).draggable({
//snap : '#droppable',
snapMode : 'outer',
revert : "invalid",
helper: function(event) { // This is a fix as helper: "original" doesn't work well
return $(this).clone().width($(this).width());
},
zIndex : 100,
handle: ".title"
});
$(elem).droppable({
//activeClass : "ui-state-hover",
//hoverClass : "ui-state-active",
drop : function(event, ui) {
var sourcePlaceholder = $( "<div/>",
{
id: "sourcePlaceholder"
}).insertBefore(ui.draggable);
var targetPlaceholder = $( "<div/>",
{
id: "targetPlaceholder"
}).insertBefore(this);
$(ui.draggable).insertBefore(targetPlaceholder);
$(this).insertBefore(sourcePlaceholder);
sourcePlaceholder.remove();
targetPlaceholder.remove();
},
tolerance : "pointer"
});
}
As you can see, the code above is part of the DIVs drag&drop implementation I'm writing using jQuery.
Hope it helps.
PS: If somebody knows how to clone elements preserving events bound using YUI, please let us know!
I have a <div> drop target that I have attached 'drop' and 'dragover' events to. The <div> contains an image inside an anchor tag (simplistically: <div><a><img /></a></div>). The child elements of the target seem to block the 'drop' or 'dragover' events from being triggered. Only when the dragged element is over the target, but NOT over its child elements, are both events triggered as expected.
The behavior I would like to achieve is that the 'dragover' and 'drop' events are triggered anywhere over the target <div>, regardless of the existence of child elements.
You can disable the pointer-events in CSS for all children.
.targetDiv * {
pointer-events: none;
}
Somewhat oblique to the original question, but I googled my way here wondering the following:
How do I make an <img />, which is the child of a <div draggable="true">...</div> container, behave correctly as a handle with which to move that container?
Simple: <img src="jake.jpg" draggable="false" />
Bastiaan's answer is technically correct though it was specific to the OP. This is a much cleaner and universal approach:
[draggable] * {pointer-events: none;}
The drag & drop spec is seriously flawed and quite hard to work with.
By tracking child elements it's possible to achieve the expected behaviour (of child-elements not interfering).
Below is a short example of how to do this (with jQuery):
jQuery.fn.dndhover = function(options) {
return this.each(function() {
var self = jQuery(this);
var collection = jQuery();
self.on('dragenter', function(event) {
if (collection.size() === 0) {
self.trigger('dndHoverStart');
}
collection = collection.add(event.target);
});
self.on('dragleave', function(event) {
collection = collection.not(event.target);
if (collection.size() === 0) {
self.trigger('dndHoverEnd');
}
});
});
};
jQuery('#my-dropzone').dndhover().on({
'dndHoverStart': function(event) {
jQuery('#my-dropzone').addClass('dnd-hover');
event.stopPropagation();
event.preventDefault();
return false;
},
'dndHoverEnd': function(event) {
jQuery('#my-dropzone').removeClass('dnd-hover');
event.stopPropagation();
event.preventDefault();
return false;
}
});
Credits:
'dragleave' of parent element fires when dragging over children elements
Something else must be going on with your code. I mocked up a simple example here:
http://jsfiddle.net/M59j6/6/
and you can see that child elements don't interfere with the events.
I have a draggable with a custom helper. Sometimes the helper is a clone and sometimes it is the original element.
The problem is that when the helper is the original element and is not dropped on a valid droppable it gets removed. My solution looks like this so far:
in my on_dropped callback I set ui.helper.dropped_on_droppable to true;
In the stop callback of the draggable, I check for that variable and then ... what do I do?
$('.my_draggable').draggable({
stop : function(e, ui) {
if (!ui.helper.dropped_on_droppable) {
/* what do I do here? */
}
},
Is this even the right approach?
Ok, I found a solution! It's ugly and it breaks the 'rules of encapsulation,' but at least it does the job.
Remember this is just for special cases! jQuery can handle its own helper removal just fine. In my case I had a helper that was sometimes the original element and sometimes a clone, so it wasn't always appropriate to delete the helper after reverting.
element.draggable({
stop : function(e, ui) {
/* "dropped_on_droppable" is custom and set in my custom drop method
".moved_draggable" is custom and set in my custom drag method,
to differentiate between the two types of draggables
*/
if (!ui.helper.dropped_on_droppable & ui.helper.hasClass('moved_draggable')) {
/* this is the big hack that breaks encapsulation */
$.ui.ddmanager.current.cancelHelperRemoval = true;
}
},
Warning: this breaks encapsulation and may not be forwards compatible
I might be missing something here, but is it not simply a case of adding
revert: "invalid"
to the options of the draggable if the draggable is of an original element, not a clone?
I use a custom helper that aggregates multiple-selection draggables into a single div. This does not seem to jive with the revert functionality so I came up with this scheme. The elements are manually appended back to the original parent which I keep track of via .data().
.draggable({
helper: function() {
var div = $(document.createElement('div'))
.data('lastParent', $(this).parent());
return div;
},
start: function() {
//... add multiple selection items to the helper..
},
stop: function(event,ui) {
$( $(ui.helper).data('lastParent') ).append( $(ui.helper).children() );
}
}
This approach does lose out on the pretty animation, but it may be useful to you or someone else with this issue.