CKEditor 4 and jQuery UI sortable removes content after sorting - jquery-ui

I've ran into an issue with CKEditor 4 and jQuery UI's sortable method where if I sort a container that has a CKEditor instance, it removes the value and throws an error "Uncaught TypeError: Cannot call method 'getSelection' of undefined". It also makes the editor uneditable. I was able to get around this in CKEditor 3 with one of the following hacks found here:
CKEditor freezes on jQuery UI Reorder
In looking at the Chrome DOM inspector, it appears that the contents of the iframe are removed.
Below is crude test code:
<html>
<head>
<title>test</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.8.24/jquery-ui.min.js"></script>
<script src="ckeditor.js"></script>
<script type="text/javascript">
$(function(){
var tmpStore = {};
$('#sortable').sortable({
cursor: 'move',
// Hack that use to work on V3 but not on V4:
// https://stackoverflow.com/questions/3379653/ckeditor-freezes-on-jquery-ui-reorder
start:function (event,ui) {
$('textarea').each(function(){
var id = $(this).attr('id');
tmpStore[id] = CKEDITOR.instances[id].getData();
})
},
stop: function(event, ui) {
$('textarea').each(function(){
var id = $(this).attr('id');
CKEDITOR.instances[id].setData(tmpStore[id]);
})
}
});
$('textarea').each(function(){
var ckId = $(this).attr('id');
config = {};
CKEDITOR.replace(ckId, config);
})
})
li { padding: 10px; width: 800px; height: 300px; }
</head>
<body>
<ul id="sortable">
<li><textarea id="test1" name="test1">test1</textarea></li>
<li><textarea id="test2" name="test1">test2</textarea></li>
<li><textarea id="test3" name="test1">test3</textarea></li>
</ul>
</body>
</html>

I was facing the same problem and have fixed based on answers here. Please see fiddles below
ISSUE:
https://jsfiddle.net/33qt24L9/1/
$(function() {
$( "#sortable" ).sortable({
placeholder: "ui-state-highlight"
});
CKEDITOR.replace( 'editor1' );
CKEDITOR.replace( 'editor2' );
CKEDITOR.replace( 'editor3' );
CKEDITOR.replace( 'editor4' );
});
RESOLVED ISSUE: https://jsfiddle.net/57djq2bh/2/
$(function() {
$( "#sortable" ).sortable({
placeholder: "ui-state-highlight",
start: function (event, ui)
{
var id_textarea = ui.item.find(".ckeditor").attr("id");
CKEDITOR.instances[id_textarea].destroy();
},
stop: function (event, ui)
{
var id_textarea = ui.item.find(".ckeditor").attr("id");
CKEDITOR.replace(id_textarea);
}
});
CKEDITOR.replace( 'editor1' );
CKEDITOR.replace( 'editor2' );
CKEDITOR.replace( 'editor3' );
CKEDITOR.replace( 'editor4' );
});
EDIT: If like me you had seperate configs per editor here's updated code that will help:
start: function (event, ui)
{
$('.wysiwyg', ui.item).each(function(){
var tagId = $(this).attr('id');
var ckeClone = $(this).next('.cke').clone().addClass('cloned');
ckeConfigs[tagId] = CKEDITOR.instances[tagId].config;
CKEDITOR.instances[tagId].destroy();
$(this).hide().after(ckeClone);
});
},
stop: function(event, ui) {
// for each textarea init ckeditor anew and remove the clone
$('.wysiwyg', ui.item).each(function(){
var tagId = $(this).attr('id');
CKEDITOR.replace(tagId, ckeConfigs[tagId]);
$(this).next('.cloned').remove();
});
}
Thanks to: https://github.com/trsteel88/TrsteelCkeditorBundle/issues/53

You have to re-create CKEditor once underlying DOM structure is modified. Save editor data with editor.getData() before editor.destroy() and restore contents with editor.setData( data ) once you create a new instance. There's no other way to fix this since CKEditor strongly depends on the DOM structure.

Remove CKEditor start Sortable
var ckeConfigs = [];
$('.ui-sortable').sortable({
start:function (event,ui) {
$('.lineItemCommentBox', ui.item).each(function(){
var tagId = $(this).attr('id');
ckeConfigs[tagId] = CKEDITOR.instances[tagId].config;
CKEDITOR.instances[tagId].destroy();
});
},
stop: function(event, ui) {
$('.lineItemCommentBox', ui.item).each(function(){
var tagId = $(this).attr('id');
CKEDITOR.replace(tagId, ckeConfigs[tagId]);
});
}
});

The code below works for me, we have to destroy the editor on start and recreate it when the drag is ended getting the value of the textarea which come the data from :
jQuery(function($)
{
var panelList = $("#nameofyourdiv");
panelList.sortable(
{
handle: ".classofyourdivforsorting",
start: function (event, ui)
{
var id_textarea = ui.item.find("textarea").attr("id");
CKEDITOR.instances[id_textarea].destroy();
}
stop: function (event, ui)
{
var id_textarea = ui.item.find("textarea").attr("id");
CKEDITOR.replace(id_textarea);
}
});
});
Hope it helps someone.

i ve solved this kind of problem by instantiating the CKEditor after having opened the jquery dialog

I had the Similar issue with CKEDITOR , The Code Below worked for me. Destroy the Ckeditor instance and Remove the Ckeditor and when the dragging ends replace the current textarea with Ckeditor again
$("#sortable").sortable({
items: '.dynamic',
start: function (event , ui) {
var editorId = $(ui.item).find('.ckeditor').attr('id');// get the id of your Ckeditor
editorInstance = CKEDITOR.instances[editorId]; // Get the Ckeditor Instance
editorInstance.destroy(); // Destroy it
CKEDITOR.remove(editorId);// Remove it
},
stop: function(event, ui) {
var editorId = $(ui.item).find('.ckeditor').attr('id');// Get the Id of your Ckeditor
CKEDITOR.replace(editorId);// Replace it
}
}
});
$("#sortable").disableSelection();
Here #sortable is the id of the DIV which is sortable and '.dynamic' is the Class assigned to DIV that are eligible to sort and '.ckeditor' is the class for the Textarea .
I got my solution from Here , Hope this helps for someone in future.

I simply use the ckeditorOff() and ckeditorOn() functions to keep data and re/de-instance ckeditor instances during movement.
$('#sortable').sortable({
cursor: 'move',
start:function (event,ui) {
if(typeof ckeditorOff=='function')ckeditorOff();
},
stop: function(event, ui) {
if(typeof ckeditorOn=='function')ckeditorOn();
}
});
I put the typeof ckeditorOff statement to make the code compatible with future versions of ckeditor in case they decide to remove these two functions.

Related

Jquery-ui append template on drop and make appended templates child as droppable(nested)

How to Append template while dropping and dropped template's child as droppable(nested).
$template=$("<div class="static">box1</div><div class="droppable-box-nested">box2</div>");
Need to append above code to my below fiddle while dropping and box2 is droppable.
Jsfiddle
You have some syntax issues in your $template, it should be something like:
var $template = $("<div class='static'>Box 1</div><div class='droppable-box-nested'>Box 2</div>");
The use of " breaks out of your string and you should use '.
Working example: https://jsfiddle.net/Twisty/vwyd9cz1/1/
JavaScript
$(function() {
var $template = $("<div class='static'>Box 1</div><div class='droppable-box-nested'>Box 2</div>");
$('.dragItem').draggable({
helper: 'clone',
connectToSortable: "#column2,#column2 div"
});
$('.dragItem').sortable({
containment: "parent"
});
$('#column2').sortable({
placeholder: "highlight"
});
$('#column2').droppable({
accept: '.dragItem',
drop: function(event, ui) {
var draggable = ui.draggable;
var droppable = $(this);
var drag = $('#column2').has(ui.draggable).length ? draggable : draggable.clone().draggable({});
drag.appendTo(column2);
$template.insertAfter(drag);
drag.sortable({
placeholder: "highlight"
});
drag.droppable({
accept: ".dragItem",
drop: function(event, ui) {
var draggable = ui.draggable;
var droppable = $(this);
var drag = $('#column2').has(ui.draggable).length ? draggable : draggable.clone().draggable({});
}
})
drag.css({
width: '',
height: ''
})
}
});
});

JQueryMobile: pagecontainershow on a particular page not working

JQueryMobile 1.4 has deprecated the pageshow event and instead recommends using pagecontainershow; however, while I'm able to get the pagecontainershow event at a document level, I can't bind a function to a particular page.
<div id="page1" data-role="page">
...
<script>
$( "#page1" ).on( "pagecontainershow", function( event, ui ) {
console.log("page1 pagecontainershow");
} );
</script>
</div>
Demonstration: http://jsbin.com/IFolanOW/22/edit?html,console,output
I also considered using the alternative form of the jQuery "on" function where we use a selector, but that would need to be a parent of the page div, and that might include other pages, so that doesn't work.
As a workaround, I've done this, but it is very inefficient:
function registerOnPageShow(pageId, func) {
var strippedPageId = pageId.replace("#", "");
var e = "pagecontainershow." + strippedPageId;
// TODO why isn't it working to use $(pageId) instead of $(document)?
$( document ).off(e).on(e, null, {page: strippedPageId, f: func}, function(e, ui) {
if ($(":mobile-pagecontainer").pagecontainer("getActivePage")[0].id == e.data.page) {
e.data.f(e, ui);
}
});
}
You can get the page ID like this.
$(document).on('pagecontainershow', function(e, ui) {
var pageId = $('body').pagecontainer('getActivePage').prop('id');
});
There is currently no way to have a show/hide event on a specific page.
Here is what I'm using (jqmobile >1.4):
$(document).on("pagecontainershow", function () {
var activePage = $.mobile.pageContainer.pagecontainer("getActivePage");
var activePageId = activePage[0].id;
switch (activePageId) {
case 'loginPage':
...
break;
case 'homePage':
...
break;
case 'groupPage':
...
break;
default:
}
});
$(document).on("pagecontainershow", function(event, ui) {
var pageId = $('body').pagecontainer('getActivePage').prop('id'),
showFunc = pageId+'_show';
if (typeof MobileSite[showFunc] == 'function') {
MobileSite[showFunc]();
}
});
MobileSite is contained in an external .js file with all the show() functions.
$(document).on("pagecontainerbeforeshow", function (event, ui) {
if (typeof ui.toPage == "object") {
var crrentPage = ui.toPage.attr("id")
}
});
and you must use this code before calling Index.js !!

jqueryui Tabs with Tablesorter

I'm using jquery ui tabs with the tablesorter 2.0 plugin to obtain sort abilities on a dynamically populated html table but the sort only happens on the first tab upon page load. The other tabs do not sort or obtain the zebra striping form the tablesorter.
html:
<div id="tabs">
<ul>
<li>Ftp Only</li>
<li>Billing Only</li>
<li>Variance</li>
<li>Adj Only</li>
</ul>
</div>
I've tried:
$('#tabs').tabs({
ajaxOptions: {cache: false},
load: function()
{
$("#ReportTable").tablesorter();
}
});
Any suggestions are much appreciated.
The zebra widget only applies to visible rows, so you'll need to trigger the applyWIdgets method. And I'm going to assume you are using jQuery UI 1.10.2 and jQuery 2.0, where you can use the activate callback (demo):
$("#tabs").tabs({
activate: function (event, ui) {
var $t = ui.newPanel.find('table');
// make sure there is a table in the tab
if ($t.length) {
if ($t[0].config) {
// update zebra widget
$t.trigger('applyWidgets');
} else {
// initialize tablesorter
$t.tablesorter({
theme: 'blue',
widgets: ["zebra"]
});
}
}
}
});
Update: Oops, if the table is in the first tab, use this code (demo):
var tablesorterOptions = {
theme: 'blue',
widgets: ["zebra"]
};
$("#tabs").tabs({
create: function (event, ui) {
var $t = ui.panel.find('table');
if ($t.length) {
$t.tablesorter(tablesorterOptions);
}
},
activate: function (event, ui) {
var $t = ui.newPanel.find('table');
if ($t.length) {
if ($t[0].config) {
$t.trigger('applyWidgets');
} else {
$t.tablesorter(tablesorterOptions);
}
}
}
});

jquery/jquery ui: find each span add class to single span being dragged

I'm trying to make it so only the span that is being dragged has a class added, so far I have this but but it adds the class to all span's ...
$(function() {
$('span').draggable();
$('#container, #board').droppable({
tolerance : 'touch',
over : function() {
$('li').each(function() {
$(this).find('span').addClass('over');
});
},
drop : function() {
$('li').each(function() {
$(this).find('span').removeClass('over');
});
}
});
});
Here's the he HTML (if that helps)
<div id="container">
<div id="board">
<ul>
<li class="foo1"><span class="p1"></span></li>
<li class="foo2"><span class="p1"></span></li>
<li class="foo1"><span class="p1"></span></li>
<li class="foo2"><span class="p1"></span></li>
</ul>
</div>
</div>
you can use the start event to add the class to the element being dragged.
$('span').draggable({
start: function(event, ui) {
$(event.target).addClass('over');
}
});
and to remove it again when no longer dragged simply add a handler to the stop event as well
$('span').draggable({
start: function(event, ui) {
$(event.target).addClass('over');
},
stop: function(event, ui) {
$(event.target).removeClass('over');
}
});
You don't need the each(), just do $(this), which refers to the current element, and it should work:
over : function() {
$(this).find('span').addClass('over');
}
$(function() {
$('span').draggable();
$('#container, #board').droppable({
tolerance : 'touch',
over : function() {
$(this).find('li span').addClass('over');
},
drop : function() {
$(this).find('li span').removeClass('over');
}
});
});
$('#container, #board').droppable({
tolerance : 'touch',
over : function(e, elem) { // Params
// here is the original element we move
$(elem.draggable).find('span').addClass("over");
},
drop : function(e, elem) {
//
$(elem.draggable).find('span').removeClass('over');
however if you dont use original Dom when it is been dragging (ie. {helper: "clone" }), elem.draggable wont affect your helper. thats write your .draggable code here...
and also you can try "elem.helper"

How to return jquery autocomplete result to the separate div?

I've found here that to overwrite one of the autocomplete events. But can somebody please provide me with example how to do the same?
The appendTo option does indeed work as expected, and if you inspect at the DOM, the <ul> results element will be attached to the element. However, due to absolute positioning generated by jQueryUI, the list still appears directly under the <input>.
That said, you can override the internal _renderItem to directly append results to a completely different element, for example:
HTML
<input id="autocomplete"/>
<div class="test">Output goes here:<br/><ul></ul></div>
JavaScript
$('input').autocomplete({
search: function(event, ui) {
$('.test ul').empty();
},
source: ["something", "something-else"]
}).data('autocomplete')._renderItem = function(ul, item) {
return $('<li/>')
.data('item.autocomplete', item)
.append(item.value)
.appendTo($('.test ul'));
};
I have also created a demo to demonstrate this. Please note that the latest jQuery library has not had jQueryUI tested against it fully, so I am using the previous version which allows me to select to include jQueryUI directly with the jsFiddle options.
<div class="test">Output goes here:<br/></div>
<script>
$("input#autocomplete").autocomplete({
source: ["something", "something-else"],
appendTo: ".text",
position: { my: "left top", at: "left bottom", of: ".test" }
// other options here
});
</script>
I needed more control over where to put the data, so this is how I went about it:
$("#input").autocomplete({
minLength: 3,
source: [
"ActionScript",
"AppleScript",
"Asp"
],
response: function(event, ui) {
console.log(ui.content);
// put the content somewhere
},
open: function(event, ui) {
// close the widget
$(this).autocomplete('close');
}
});
hle's answer worked awesome for me and gives you more flexibility! Here is my test code that was modified by his answer:
$("#autocomplete").autocomplete({
minLength: 3,
source: ["something", "something-else"],
response: function(event, ui)
{
console.log(ui.content);
// put the content somewhere
},
open: function(event, ui)
{
// close the widget
$(this).autocomplete('close');
}
});
Although this question is pretty old but i got a pretty easy solution. No hack, nothing just in jQuery way:
Instead of autocomplete response function, just add response data in div on success
$(document).ready(function () {
$("#book-code-search").autocomplete({
minLength: 2,
delay: 500,
source: function (request, response) {
$.ajax( {
url: "server side path that returns json data",
data: { searchText: request.term, param2 : $("#type").val()},
type: "POST",
dataType: "json",
success: function( data ) {
$("#data-success").html(data.returnedData); //returnedData is json data return from server side response
/* response($.map(data, function (item) {
return {
label: item.FullDesc,
value: item.FullDesc
}
})) */
}
});
}
});
});
<link href="https://code.jquery.com/ui/1.12.1/themes/smoothness/jquery-ui.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<div id='data-success' style='color: green;'></div>
<input type='text' placeholder='Enter Book Code' id='book-code-search' />
<input type='hidden' id='type' value='book'>

Resources