CKEditor 4 Widget with Draggable contents - javascript

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!

Related

jQuery .html() not setting html at all

I have a simple thing. When a user clicks on the edit link it turns the previous element into an input element for editing and the edit into a cancel. If the user decides not to edit he can click on cancel and everything should revert to its initial state.
Right now this is not working at all:
$('.cancel').on('click', function() {
$(this).parent().html("<a href='#'>edit</a>");
});
HTML:
<div class='photo-section'>
<div class='photo-head'>
<div class='photo-info'>
Photo Name : <span class='photo-name'>Work selfie</span>
<span class='title-edit'><a href='#'>edit</a></span>
</div>
</div>
<div class='photo'>
<img src='' alt='' title=''>
</div>
<div class='tag-section'>
<div class='tags'>Photo Tags:
<span>#code#coffee#late#night</span>
<span class="tags-edit">edit</span>
</div>
</div>
</div>
CSS:
// JavaScript to handle photo operations
$(document).ready(function() {
// show/hide edit option
$('.photo-info, .tags').hover(function() {
$(this).find('.title-edit > a, .tags-edit > a').addClass('visible');
}, function() {
$(this).find('.title-edit > a, .tags-edit > a').removeClass('visible');
});
// show editable area
$('.title-edit, .tags-edit').on('click', function () {
edit(this);
});
});
function edit(elem) {
// change element into an input elemnt for editing
var $item = $(elem).prev();
var text = $item.text();
$item.html("<input type='text'>").find('input').attr('value', text);
// change edit to cancel if input element present
if ($('input').length) {
$item.next().html("<a href='#' class='cancel'>cancel</a>");
}
// change cancel back to edit
if ($('.cancel').length) {
$('.cancel').on('click', function() {
$(this).parent().html("<a href='#'>edit</a>");
});
}
}
Result: https://jsfiddle.net/hgwkxygz/6/
Any help would be great!
This is a very common case of attaching event at wrong time in javascript.
Actually you are removing and re-adding a DOM element. So the already attached event to .cancel won't work this time. You again have to write event listener on .cancel whenever you attach a new DOM element after clicking on edit button.
Basically it means whenever you do .html(), you have to re-add event listener for click.
There are various approach to solve this problem.
1) make a function which attach the DOM element as well as click event to that DOM element. Call that function only on click events.
2)Do event delegation.
3)Do not remove DOM elements on click events, rather hide and show elements so that you wont loose your event listeners.
4)If you really have to do remove and re-add DOM elements then in my opinion, best approach is to make a class, where you make DOM elements, add event listeners privately in that class, and on click events just make new instance of that class.
You can check out these options in detail on web.

Handling events with SVG control in Enyo 2.0 (object or embed tag)

I have this control for loading a SVG-document (works, the SVG displays fine)
enyo.kind({
name: "SvgParser",
kind:"Control",
fit: true,
published: {
source:''
},
components:[{
tag: "object", // Or Embed
name:'svgObject',
classes: 'SvgObject',
ontap:'click',
onload:'loaded'
}],
create: function() {
this.inherited(arguments);
this.sourceChanged();
},
click: function(inSender, inEvent) {
console.log('click'); // doesn't happen
},
sourceChanged: function() {
this.$.svgObject.attributes.type = 'image/svg+xml';
this.$.svgObject.attributes.data = this.source;
},
loaded: function(inSender, inEvent) {
console.log('loaded'); // doesn't happen
}
});
but the event-handlers for 'tap' and 'load' never are triggered, can somebody explain me what i'm doing wrong? Thanks in advance
You actually have two separate problems, one preventing the load handler from working and the other preventing the tap handler from working.
The load handler isn't being called because you need to tell Enyo to listen for the load event on the <object> tag. You can do this with a call to enyo.makeBubble().
The tap handler isn't being called because click/tap events on an <object> tag containing an SVG image are routed into the DOM of the image itself. To intercept them in HTML, you'll need to wrap the <object> in a transparent <div> and give the <object> a negative z-index.
I've made a fiddle illustrating both techniques here: http://jsfiddle.net/rbWMr/
For background on the SVG-wrapping technique, see Making an svg image object clickable with onclick, avoiding absolute positioning.
The events aren't being triggered because you're trying to modify DOM node attributes when the DOM node for the Enyo component hasn't been created yet. DOM nodes are created when the component is rendered. Move your sourceChanged() function to the control's rendered() function or if you need it in the control's create() function, call hasNode() on the control to force it to create the DOM node first. Try this version of your sourceChanged() function and see if it works:
sourceChanged: function() {
var svgObj = this.$.svgObject;
if (svgObj.hasNode()) {
svgObj.setAttribute("type", 'image/svg+xml');
svgObj.setAttribute("data", this.source);
}
},

Attach event handler to button in twitter bootstrap popover

I am using the twitter bootstrap popovers,
In the popover I am adding a button,
I need to attach a click handler to the button,
but the way popover works is each time it shows it removes and re-creates the element, instead of just showing/hiding it, hence removing any event handlers I have associated with said button.
I am creating several popovers all with their own version of the button, so just applying a class to the popover won't work (unless I generate a different class for each one :/), the button may or may not have it's own ID, so cannot apply an ID.
How can I apply a event handler to something in the contents of the twitter bootstrap popover?
I had the same problem, and, in my case, the $(body).on('click') workaround won't work, since the application has lotta click buttons.
I did the following instead. This way, we can limit the scope of the delegate event to just the parent of the popover element.
$('a.foo').popover({
html: true,
title: 'Hello',
placement: 'bottom',
content: '<button id="close-me">Close Me!</button>'
}).parent().delegate('button#close-me', 'click', function() {
console.log('World!');
});
JS Fiddle: http://jsfiddle.net/dashk/5Yz7z/
P.S. I used this technique in within a Backbone.View in my application. Here's the snippet of the code in Fiddle, in case you're using this in Backbone.js as well...: http://jsfiddle.net/dashk/PA6wY/
EDITED
In Bootstrap 2.3, you can specify a target container which popover will be added to. Now, instead of doing the .parent() locator, you can also listen to events specifically to the container... This can make the listener even more specific (Imagine creating a DIV that only exists to contain the popover.)
This should do it. This will take care of any existing and future button elements created inside an element with the .popover class:
$('body').on('click', '.popover button', function () {
// code here
});
Very simple solution that worked for me is:
// suppose that popover is defined in HTML
$('a.foo').popover();
// when popover's content is shown
$('a.foo').on('shown.bs.popover', function() {
// set what happens when user clicks on the button
$("#my-button").on('click', function(){
alert("clicked!!!");
});
});
// when popover's content is hidden
$('a.foo').on('hidden.bs.popover', function(){
// clear listeners
$("#my-button").off('click');
});
Why this works:
Basically popover's content has no listener until the popover is opened.
When popover is shown, bootstrap fires its event shown.bs.popover. We can attach an event handler on this event to $(a.foo) using the jQuery on method. So when popover is shown, the handler (callback) function is called. In this callback, we can attach event handlers to the content of the popover - for example: what happens when user clicks on this button inside the popover.
After popover is closed, it is good idea to remove all attached handlers to the popover's content. This is done via hidden.bs.popover handler, which removes handlers with jQuery .off method. This prevents event handlers inside the popover to be called twice (and more) when the popover is opened again...
Just to slightly update DashK's very good answer: .delegate() has been superseded by .on() as of jQuery 1.7 (see here).
$('a.foo').popover({
html: true,
title: 'Hello',
placement: 'bottom',
content: '<button id="close-me">Close Me!</button>'
}).parent().on('click', 'button#close-me', function() {
console.log('World!');
});
See jsfiddle here: http://jsfiddle.net/smingers/nCKxs/2/
I have also had some issues with chaining the .on() method to $('a.foo'); if you experience such an issue, try adding it to the document, body, or html, e.g.:
$('a.foo').popover({
html: true,
title: 'Hello',
placement: 'bottom',
content: '<button id="close-me">Close Me!</button>'
});
$('body').on('click', 'button#close-me', function() {
console.log('World!');
});
$('.btn-popover').parent().delegate('button#close-me','click', function(){
alert('Hello');
});
If data attributes are set in static html the above method works fine. Thanks :)
You can get into trouble if you add more complex structure in popover's content, for example an external component. For this case, this should do the trick.
var content = $('<button>Bingo?</button>');
content.on('click', function() {
alert('Bingo!')
});
$('a.foo').popover({
html: true,
content: content
}).on('hidden.bs.popover', function() {
content.detach(); # this will remove content nicely before the popover removes it
});
try this
var content = function() {
var button = $($.parseHTML("<button id='foo'>bar</button>"));
button.on('click', '#foo', function() {
console.log('you clicked me!');
} );
return button;
};
$( '#new_link' ).popover({
"html": true,
"content": content,
});

jquery draggable and resizable is not working on cloned div

I have a div with draggable and resizable functionality of jQuery UI. I clone the div and append it to the same parent:
<div class="drag resize"> ... </div>
$(".drag").live("mouseenter", function() { $(this).draggable(); });
$(".resize").live("mouseenter", function() { $(this).resizable(); });
$("div").clone(true).appendTo($("div").parent());
The clone is created successfully, but when I am trying to drag it, the orignal one drags. Resizing is also not working. Can anybody explain me what is happening here?
You might like to look at this SO post jQuery UI resizable cloned element(.clone(true)) doesn't resize
- the removal of the resize handlers before applying a resizable() on the cloned element worked for me
I would use the .on event handler instead and attach it to the parent of this div.
$(".drag").parent().on("mouseenter", ".drag", function() { $(this).draggable(); });

jQuery draggable for live events on iPad

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.

Categories