CKEditor get range to insert HTML at custom location - javascript

I am working on a module CKEditor Responsive Plugin for Drupal 7. I would need to insert a piece of HTML at a custom location above the cursor position. Below is the image which shows current cursor position:
The HTML of above part of the code looks like this:
<div class="ckeditor-col-container">
<div class="hundred-hundred-fifty-fifty-thirtythree-thirtythree">
<div class="grid-12 twelvecol">
<p>lorem ipsum</p>
</div>
</div>
<div class="hundred-hundred-fifty-fifty-thirtythree-thirtythree">
<div class="grid-12 twelvecol">
<p>lorem ipsum</p>
</div>
</div>
<div class="hundred-hundred-fifty-fifty-thirtythree-thirtythree">
<div class="grid-12 twelvecol">
<p>lorem ipsum</p>
</div>
</div>
</div>
<p><br />
Sri Ramakrishna Vidya Kendra
</p>
<p></p>
The three div's which you see is the location which I now want to insert - that means I need to append my new set of div's below the last child div of the div with class ckeditor-col-container
I have gone through this SO link which talks about inserting HTML in given range: Insert HTML before an element in CKEditor
However, following are the challenges which I was not able to solve:
Traverse through the DOM above current cursor location to prepare the range with respect to the nearest div with class ckeditor-col-container
Get to the end of this (ckeditor-col-container) DOM and prepare range so that the new HTML elements would be inserted inside the scope of ckeditor-col-container
The DOM structure above cursor can be nested, but I am interested to find the nearest div with class ckeditor-col-container irrespective of the complicated nested DOM structures.
This is relatively easy to achive using jQuery object and traversing through DOM but CKEditor is cryptic with comparatively very less literature. Also point 3 in the above is tricky as a hierarchical datastructure has to be read in a flat fashion.
Any help would be appreciated.
Edit:
The example HTML piece of code which I want to insert is the same div's which you can find above:
lorem ipsum
And the final HTML will be like this:
<div class="ckeditor-col-container">
<div class="hundred-hundred-fifty-fifty-thirtythree-thirtythree">
<div class="grid-12 twelvecol">
<p>lorem ipsum</p>
</div>
</div>
<div class="hundred-hundred-fifty-fifty-thirtythree-thirtythree">
<div class="grid-12 twelvecol">
<p>lorem ipsum</p>
</div>
</div>
<div class="hundred-hundred-fifty-fifty-thirtythree-thirtythree">
<div class="grid-12 twelvecol">
<p>lorem ipsum</p>
</div>
</div>
.
.
.
<div class="hundred-hundred-fifty-fifty-thirtythree-thirtythree">
<div class="grid-12 twelvecol">
<p>lorem ipsum</p>
</div>
</div>
.
.
.
</div>
<p><br />
Sri Ramakrishna Vidya Kendra
</p>
<p></p>
The new 'inserted' div is the one which is displayed between the dots. I did not find a way to highlight the code when it is code-formatted.

The code doesn't do a complete traversing, but I think it can give you a pretty good place to start with.
The general idea is to take the current position of the cursor and start to check if any of the siblings (up and down the DOM tree) is the element we are looking for.
CKEDITOR.plugins.add( 'samplePlugin', {
icons: 'samplePluginIcon',
init: function( editor ) {
editor.addCommand( 'samplePlugin', {
exec: function( editor ) {
// First we need to find where our cursor is
var selection = editor.getSelection();
var range = selection.getRanges()[0];
// We go up and down the DOM tree, so we need the prev and next elements
var prevNode = range.getPreviousNode();
var nextNode = range.getNextNode();
// Save the container we are looking for
var container = null;
while (prevNode || nextNode) {
while (prevNode && prevNode.type == CKEDITOR.NODE_TEXT) {
prevNode = prevNode.getPreviousSourceNode();
}
if (prevNode && prevNode.hasClass('ckeditor-col-container')) {
container = prevNode;
break;
} else if (prevNode) {
prevNode = prevNode.getPreviousSourceNode();
}
while (nextNode && nextNode.type == CKEDITOR.NODE_TEXT) {
nextNode = nextNode.getNextSourceNode();
}
if (nextNode && nextNode.hasClass('ckeditor-col-container')) {
container = nextNode;
break;
} else if (nextNode) {
nextNode = nextNode.getNextSourceNode();
}
}
// In case we found the container we are looking for - just append some HTML to it.
if (container) {
container.appendHtml('<div class="hundred-hundred-fifty-fifty-thirtythree-thirtythree">'+
'<div class="grid-12 twelvecol">'+
'<p>lorem ipsum</p>'+
'</div>'+
'</div>')
}
}
});
editor.ui.addButton( 'samplePlugin', {
label: 'samplePlugin',
command: 'samplePlugin',
toolbar: 'insert'
});
}
});

Related

How to get all elements but div (for example), including nested ones?

This is probably a long shot but is there a way to get a collection of elements in JQuery or not without a specific tag/class/id, even the nested ones?
Let's say I have this piece of code:
<div class="container1">
Lorem ipsum dolor sit amet
<br><br>
</div>
<p>This is p 1
<div class="footer">
tessssst
<p>
p test
</p>
</div>
</p>
<div class="container">
tessst
</div>
<p>This is p 2
<div id="someID" class="container">
tessssst 2
</div>
</p>
Now, I'm using JQuery like this, in order to find all but div tags, including descendants:
$('body').find(':not(div, div *)')
In the result collection, I still get the div inside the p elements and I don't want that.
Is there any way to achieve that? I know that this div is part of h1 and since I just want to select elements and not removing or doing DOM manipulations it could be a weird thing to wish for but this is what I need.
The bigger problem - I need to retrieve all text nodes but to exclude some tags/classes/IDs. I'm doing so as suggested here but it's not good enough.
Thanks.
The code you provided seems to work, but maybe you could try this?
$('body').find("*").filter(":not(div)");
There is a jQuery function not(), which removes matching elements from the current set.
So all you need to do is
var nonDivs = $('body').find('*').not('div');
You can do that by first selecting all elements, then selecting <div>s and their descendants, and filtering the "all" results with the "divs" result using .not() (the red style is used to mark the matched set):
var all = $('body').find('*');
var divAll = $('body').find('div, div *');
var nonDiv = all.not(divAll);
nonDiv.css('color', 'red');
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container1">
Lorem ipsum dolor sit amet
<br><br>
</div>
<p>This is p 1
<div class="footer">
tessssst
<p>
p test
</p>
</div>
</p>
<div class="container">
tessst
</div>
<p>This is p 2
<div id="someID" class="container">
tessssst 2
</div>
</p>
In the filter function check if it's a text node and also check all of its parents for class, id, tag name - whatever selectors you would like to filter by.
var textnodes = $('body').find("*").contents()
.filter(function () {
return (this.nodeType === 3 && $(this).parents("div").length <= 0);
})
.each(function () {
console.log($(this).text());
});
Here's a working example: https://jsfiddle.net/hracw15o/3/

jQuery Traversal -- Find an Element Relative to Common Ancestor

I frequently find I'm needing to select an element that is nearby, typically within a common container, but which is not a sibling or within the same "tree line". For example, given this HTML:
<div id="container-left" class="container">
<div class="sidebar">
<button class="more-link">Show Extras</button>
</div>
<div class="content">
<div class="tidbits">
<p>Lorem ipsum beep bop boop</p>
<p class="extra hidden">Exxtra info about lorem ipsum!</p>
</div>
</div>
</div>
<div id="container-right" class="container">
<div class="sidebar">
<button class="more-link">Show Extras</button>
</div>
<div class="content">
<div class="tidbits">
<p>Lorem ipsum beep bop boop</p>
<p class="extra hidden">Exxtra info about lorem ipsum!</p>
</div>
</div>
</div>
What I'll do is attach an event listener to the "Show Extras" buttons, which target the p tags with class of extra, and on click, toggle the hidden class. So (using jQuery) I typically select like this:
$(".more-link").on("click",function(){
var $this = $(this);
var $extraElement = $this.closest(".container").find(".extra");
$extraElement.toggleClass("hidden");
});
My question: is there a better way to select the extra element than the .closest().find() combo? Something about it just feels a little clunky.
well.. you could navigate to sidebar siblings and find extra... but the mess would be similar...
Other way to do it is to generate a HTML5 data attribute at button:
<button class="more-link" data-extra='#some-generated-extra-id'>Show Extras</button>
...
<p id='some-generated-extra-id' class="extra hidden">Exxtra info about lorem ipsum!</p>
your code:
$(".more-link").on("click",function() {
var $extraElement = $($(this).data('extra'));
$extraElement.toggleClass("hidden");
});

Wrap every immediate group of objects, including text, except divs inside a new div

I'm trying to wrap every immediate group of objects that is not a div inside a wrap class. Is there any way
Input:
var $code =
'Lorem Ipsum
<p>Foo Bar</p>
<div class="myclass"></div>
<p>Foo Bar</p>
<div class="myclass"></div>'
var $object = $('<div/>').html($code);
Wanted output:
<div class="wrap">
Lorem Ipsum
<p>Foo Bar</p>
</div>
<div class="myclass"></div>
<div class="wrap">
<p>Foo Bar</p>
</div>
<div class="myclass"></div>
What I've tried:
$object.contents().not('> .myclass').wrap('<div class="wrap"></div>');
Can someone help me please? I'm stuck here.
Thank you very much!
Haven't checked whether there is a more optimal way to do it, but there you go
var code =
'Lorem Ipsum<p>Foo Bar</p><div class="myclass"></div><p>Foo Bar</p><div class="myclass"></div>'
var $object = $('<div/>').html(code);
var $obj = $();
$object.contents().each(function () {
if ($(this).hasClass('myclass')) {
$obj.wrapAll('<div class="wrap"/>');
$obj = $();
} else {
$obj = $obj.add(this)
}
})
$obj.wrapAll('<div class="wrap"/>');
Demo: Fiddle

Expand/Collapse HTML divs simultaneously

I am trying to create a row of HTML divs that expand and collapse when you click on them. Please see this jsFiddle to get an idea of what I mean: http://jsfiddle.net/Lm6Pg/3/. (You may have to expand the result pane or use the full screen result to get all the divs to appear on a single row.)
Currently, I am using One% CSS Grid to get all the divs to appear on one row, then toggling different CSS column classes to expand and collapse the divs according to the current state and what div was clicked.
<div id="content" class="onepcssgrid-1200">
<div class="onerow">
<div class="tile col4" id="about">
<h3>About</h3>
<div class="text">
<p>Lorem ipsum dolor sit amet.</p>
</div>
</div>
<div class="tile col4" id="other">
<h3>Other</h3>
<div class="text">
<p>Lorem ipsum dolor sit amet.</p>
</div>
</div>
<div class="tile col4 last" id="stuff">
<h3>Stuff</h3>
<div class="text">
<p>Lorem ipsum dolor sit amet.</p>
</div>
</div>
</div>
Here is my javascript (I've changed it from what's in the jsFiddle because I was messing around with dynamically determining which column classes needed to be toggled):
$(".tile").click(function() {
var tile = $(this);
var otherTiles = tile.siblings();
var currentSelectedTile = otherTiles.filter(".selected");
var unselectedOtherTiles = otherTiles.not(".selected");
var otherTileWeight, tileWeight;
if (currentSelectedTile.length) {
otherTileWeight = 3;
tileWeight = 3;
} else {
otherTileWeight = 4;
tileWeight = 4;
}
tile.toggleClass("selected col" + tileWeight + " col6", 600);
currentSelectedTile.toggleClass("selected col3 col4", 600);
unselectedOtherTiles.toggleClass("col" + otherTileWeight + " col3", 600);
});
This seems like a lot of code that might be wrapped up in a jQuery UI function or some other library that eluded me when I put this together. Is there an easier, more concise, or just plain better way to do this? The solution does not need to use jQuery nor One% CSS Grid, any library is fine. However, it does need to be in a responsive layout that preferably still functions as you'd expect when the divs are on top of each other.

Copy and inserting HTML elements into new pop-up block

I want to completely copy all elements
<div id="articleFull"> ... </div>
(+ div inclusive) with their content in a new pop-up window
<div id="newPopUp"> ... </div>
<div id="articleFull">
<p>lorem ipsum</p>
<img src="1.png" />
<p>lorem ipsum</p>
<p>lorem ipsum</p>
<h3>Test title</h3>
<img src="1.png" />
<p>lorem ipsum</p>
</div>
I tried to do this simple method:
http://jsfiddle.net/ApBSN/3/
articleFull = document.getElementById('articleFull');
function copyHtml(){
div = document.createElement('div')
div.id = 'newPopUp';
document.body.appendChild(div);
var t = document.getElementById('articleFull');
div.appendChild(t);
}
It works... BUT the function does not copy the code, and moves it from one place to another, effectively removing it from its original location. I just want to duplicate the block. Yes, I understand that the page can not be 2 "ID", but with this, I'll take care of myself more.
Ideas?
you can try Clone if interested in Jquery...http://api.jquery.com/clone/ this will duplicate the html rather then replacing it as in case of append
i have updated your http://jsfiddle.net/ApBSN/9/ but now you need to work on css
var t1 = document.getElementById('newPopUp');
var t = document.getElementById('articleFull');
$(t).clone().appendTo(t1);
If I understood correctly this should do it:
function copyHtml(){
div = document.createElement('div')
div.id = 'newPopUp';
document.body.appendChild(div);
var t = document.getElementById('articleFull');
t.id = "articleFull2";
div.appendChild(t);
}

Categories

Resources