Get all IDs of selected/highlighted DIVs - javascript

I may be going in the completely wrong direction with what I'm trying to do, so I wanted to ask for help.
Background / Overview
I need to display a paragraph of text and allow a user to select one or more words from the paragraph and save their highlighted text to a database, for just their profile. Actually, hat selection of text will eventually be (1) stored with the highlight AND (2) linked up to another set of highlighted text from another paragraph (basically, I'm tying a phrase from one source to a reference source)
What I've tried...
I have tried to put each word of the paragraph into a DIV (and a unique ID) with each DIV set to float left, so that the display looks okay.
<style>
div { float: left}
</style>
and...using an example:
<div id="GEN_1_1">
<div id="GEN_1_1_1">In</div>
<div id="GEN_1_1_2">the</div>
<div id="GEN_1_1_3">beginning</div>
<div id="GEN_1_1_4">God</div>
<div id="GEN_1_1_5">created</div>
<div id="GEN_1_1_6">the</div>
<div id="GEN_1_1_7">heaven</div>
<div id="GEN_1_1_8">and</div>
<div id="GEN_1_1_9">the</div>
<div id="GEN_1_1_10">earth</div>.
</div>
Which looks like: In the beginning God created the heaven and the earth. (minus the bold)
So far, I have used the
window.getSelection()
function to determine/grab the words that have been highlighted.
I then tried using this:
if (window.getSelection)
{
selected_len = window.getSelection().toString().length;
if (window.getSelection().toString().length>0)
{
div_id = window.getSelection().getRangeAt(0).commonAncestorContainer.parentNode.id;
}
}
to get the ID's for each DIV selected, BUT I only get a single DIV ID returned right now.
Help Request:
Is there are slick way to get the ID for each DIV selected and put it into an Array, so that I can construct a SQL query to put it into the database (the query is easy)? The selected words could total up to several hundred, if not a thousand words, so I need to make sure the solution will work with a ton of words selected.

UPDATE: JSFIDDLE DEMO
I modified the code again. See if it works for you now.
$(document).ready(function () {
$(document).on('mouseup keyup', '.checked', function () {
console.log(window.getSelection());
if (window.getSelection().toString().length>0) {
var count = window.getSelection();
var arr = [];
$('.checked span').each(function(){
var span = $(this)[0];
var isT = window.getSelection().containsNode(span, true);
if(isT){
arr.push($(this).attr('id'));
}
});
console.log(arr,count.toString());
alert(arr);
alert(count.toString());
}
});
});
I created a fiddle for solution. Check it out here: http://jsfiddle.net/lotusgodkk/GCu2D/18/
Also, I used span instead of div for the text selection. I hope that won't be an issue for you. So the code works as you want. It will return the id of the parent span in which text is selected. You can modify it to save the ID into array or as per your needs.
$(document).ready(function () {
$(document).on('mouseup', '.checked', function () {
if (window.getSelection) {
var i = getSelectionParentElement();
console.log(i);
alert('parent selected: ' + i.id);
}
});
});
function getSelectionParentElement() {
var parent = null, selection;
if (window.getSelection) {
selection = window.getSelection();
if (selection.rangeCount) {
parent = selection.getRangeAt(0).commonAncestorContainer;
if (parent.nodeType != 1) {
parent = parent.parentNode;
}
}
} else if ((selection = document.selection) && selection.type != "Control") {
parent = selection.createRange().parentElement();
}
return parent;
}

Related

How to add all containers in a selection to an array

I am trying to get all the containers in a selection and add them into an array. So far, I have been able to get only the first container using the following code:
function getSelectedNode()
{
var containers = [];//need to add containers here so we can later loop on it and do the transformations
if (document.selection)
return document.selection.createRange().parentElement();
else
{
var selection = window.getSelection();
if (selection.rangeCount > 0)
return selection.getRangeAt(0).startContainer.parentNode;
}
}
So if I had:
<p>
<b>Here's some more content</b>.
<span style="background-color: #ffcccc">Highlight some</span>
and press the button. Press the other button to remove all highlights
</p>
and I selected this part of the text:
"Here's some more content Highlight"
Once I use the container returned by getSelectedNode() and do some transformation on it only "Here's some more content" gets affected correctly and not "Highlight". So is there a way to make it get all containers and not just the first one?
Note: I was also previously looking at this link:
How can I get the DOM element which contains the current selection?
and someone even commented:
"This solution doesn't work for all cases. If you try to select more than one tag, all the subsequent tags except the first one will be ignored."
Use Range.commonAncestorContainer and Selection.containsNode:
function getSelectedNode()
{
var containers = [];//need to add containers here so we can later loop on it and do the transformations
if (document.selection)
return document.selection.createRange().parentElement();
else
{
var selection = window.getSelection();
if (selection.rangeCount > 0) {
var range = selection.getRangeAt(0);
if (range.startContainer === range.endContainer) {
containers.push(range.startContainer);
} else {
var children = range.commonAncestorContainer.children;
containers = Array.from(children || []).filter(node => selection.containsNode(node, true));
}
}
}
return containers;
}
In your case, all possible "containers" are siblings that have no children, and we are selecting using a mouse or keyboard. In this case, we only have to consider two possibilities: you've selected a single node, or you've selected sibling nodes.
However, if your HTML were more complicated and you considered the possibility of scripts creating multiple selections, we'd have need a different solution. You would have to go through each node in the DOM, looking for ones that were part of something selection.
Maybee i am blondie and old school but if i have to fill a array i use a for next loop and something called push to fill the array. That might not be cool but usually works. I can not see any loop or pushing. So there will be only one element.`
other code
...
if (selection.rangeCount > 0)
for (var i;i<selection.rangeCount;i++){
var x= selection.getRangeAt(i).startContainer.parentNode ; //make a var
containers.push(x);//push var to array
}
return containers ;
}`
It seems that you wan't to unhighlight the selected text, it seems easer to go through the highlighted portions and see if they are part of the selection, here is an example:
document.addEventListener('mouseup', event => {
const sel = document.getSelection();
if (!sel.isCollapsed) {
const elms = [...document.querySelectorAll('.highlighted')];
const selectedElms = elms.filter(e => sel.containsNode(e, true));
if (selectedElms.length) {
selectedElms.forEach(e => {
let prev = e.nextSibling;
[...e.childNodes].forEach(child => e.parentElement.insertBefore(child, e));
e.remove();
});
sel.empty();
}
}
});
.highlighted {
background-color: #ffcccc
}
<p>
<b>Here's <span class="highlighted">Highlight <b>some</b></span> some more content</b>.
<span class="highlighted">Highlight some</span>
and press the button. Press the <span class="highlighted">Highlight some</span> other button to remove all highlights
</p>
Because I've used true as the second parameter to containsNode(...), this example will unhighlight the elements that are only partially selected.

Unable to un-highlight selection in 2 different paragraphs using Rangy library

JSfiddle for reference: https://jsfiddle.net/9jp346r4/20/
I am trying to create functionality that allows user to highlight the selected text upon pressing a button, and unhighlight the highlighted text upon right-clicking.
I've gotten it mostly working using the rangy library except there's one scenario that doesn't work and I'm not sure how to solve it.
When I highlight text that is in 2 different paragraphs, it highlights it successfully.
The issue arises when I would like to come back later and un-highlight both the paragraphs.
The expected behaviour is: I right-click any highlighted text regardless of if it is selected or not and it will un-highlight all nearby highlighted text even if it's separated by a paragraph tag or strong tag.
The current behaviour is: It only unhighlights the text in the paragraph I clicked.
To re-produce:
1) Select text that overlaps both the first and second paragraph and press the "Press" button.
2) Un-select the selected text by clicking somewhere else on the screen.
3) Right-click any of the highlighted text. Notice only one of the paragraphs gets un-highlighted.
If something is unclear, feel free to ask questions. Would appreciate the help.
Here is my HTML:
<div id="content">
<p>
Paragraph 1
</p>
<p>
Paragraph 2
</p>
</div>
<div id="divId">
<input id="myBtn" type="button" value="Press" onclick = "javascript:toggleItalicYellowBg()"/>
</div>
Here is my javascript:
function coverAll() {
var ranges = [];
for(var i=0; i<window.getSelection().rangeCount; i++) {
var range = window.getSelection().getRangeAt(i);
while(range.startContainer.nodeType == 3
|| range.startContainer.childNodes.length == 1)
range.setStartBefore(range.startContainer);
while(range.endContainer.nodeType == 3
|| range.endContainer.childNodes.length == 1)
range.setEndAfter(range.endContainer);
ranges.push(range);
}
window.getSelection().removeAllRanges();
for(var i=0; i<ranges.length; i++) {
window.getSelection().addRange(ranges[i]);
}
return true;
}
function getSelectedText() {
if (window.getSelection) {
return window.getSelection().toString();
} else if (document.selection) {
return document.selection.createRange().text;
}
return '';
}
var italicYellowBgApplier;
function toggleItalicYellowBg() {
italicYellowBgApplier.toggleSelection();
}
window.onload = function() {
$(document).on("contextmenu", ".italicYellowBg", function(e){
if(coverAll()) {
italicYellowBgApplier.undoToSelection();
return false;
}
});
rangy.init();
// Enable buttons
var classApplierModule = rangy.modules.ClassApplier;
// Next line is pure paranoia: it will only return false if the browser has no support for ranges,
// selections or TextRanges. Even IE 5 would pass this test.
if (rangy.supported && classApplierModule && classApplierModule.supported) {
italicYellowBgApplier = rangy.createClassApplier("italicYellowBg", {
tagNames: ["span", "a", "b", "img"]
});
}
};
I guess to easy solve this problem, in memory keep an array of user highlights, one item of that array is not a single highlight item, but a further "selection of selected items during the highlight", when somebody would right click on a single segment of highlight, in-memory find from array all associated highlights and unhighlight the related segments by yourself.

Show/hide div with specified text on mouseover/mouseout of copied div

I have a table which is copied verbatim from one div to another. I did this so that I could have a fixed table header with a scrollable body. The first div id is #headdiv and the second div class is .bodydiv, and the contents of #headdiv are duplicated into .bodydiv with this function:
$('.bodydiv').html($('#headdiv').html());
And then I modify the display/visibility properties of the two divs to make them look like one table. See here for the html and css: http://jsfiddle.net/jbswetnam/KNnAd/5/
Now, what I want to do is make some help text appear when the user hovers over cells in the table. I can do this with the following functions using element id's:
//Copied and modified from https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Closures
function showInfo(info, display) {
document.getElementById('infoBox').innerHTML = info;
document.getElementById('infoBox').style.display = display;
}
function makeInfoCallback(info, display) {
return function() {
showInfo(info, display);
};
}
function setupInfo() {
var infoText = [
{'id': 'header', 'info': 'Header Row'},
{'id': 'alpha', 'info': 'Alpha'}
];
for (var i = 0; i < infoText.length; i++) {
var item = infoText[i];
document.getElementById(item.id).onmouseover =
makeInfoCallback(item.info, "");
document.getElementById(item.id).onmouseout =
makeInfoCallback("", "none");
}
}
setupInfo();
As you can see at http://jsfiddle.net/jbswetnam/KNnAd/5/, when you hover over the table header, the text "Header Row" appears. What I'm trying to do is make the text "Alpha" appear when you hover over the cell that says "Alpha".
I know why the function works in the header and not the body. The header has an id which is referenced in the function above, whereas the body cells that you see are copied from #headdiv and therefore their id's are not valid. But I don't know enough about Javascript to know how to fix the problem. Using classes instead of id's doesn't work. I have a feeling that I can refactor the whole script using this and perhaps calling the function out of each cell, but I just don't know how to do that.
Any help would be appreciated!
Got it done with this code:
function makeInfo() {
$('.info').hover(
function() {
$('#infoBox').html($(this).attr('info'));
$('#infoBox').css('display', '');
},
function() {
$('#infoBox').css('display', 'none');
});
}
makeInfo();
You can see the updated HTML at the same jsfiddle.

Select hidden elements and manipulate them with jQuery

Within a div wrapper with a class of "section", I have dozens of HTML elements repeated across the page that look like this:
<div class="section">
<div class="article"></div>
<div class="article"></div>
<div class="article"></div>
</div>
And each contains certain information inside. Now, what I'm trying to do is once the page loads, show only the first 5, hide the rest in a new div inserted with jQuery, and when this new div is clicked it will display the next five , and then the next five on click again, and so on until the end. The idea is that this new div will function as a button that will always be positioned at the end of the page and will respond to these orders I just mentioned. So far I've got this down:
$('.section').each(function () {
var $this = $(this),
$allArticles = $this.find('.article');
if ($allArticles.length > 5) {
$('<div/>').addClass('hidden-articles').appendTo(this).append($allArticles.slice(5));
$('.hidden-articles .article').hide();
}
});
And that hides all but the first five. But now for the rest of the process, I can't get it to work. I don't seem to be able to select properly those hidden div with class "article" and manipulate them to function the way I described above. I would appreciate it a lot if someone more experienced with jQuery could guide me in the right direction and maybe offer a snippet. Many thanks in advance!
You can use the :hidden and :lt selectors to get the functionality you are looking for..
$('.section').each(function() {
var $this = $(this),
$allArticles = $this.find('.article');
if ($allArticles.length > 5) {
$('<div/>').addClass('hidden-articles')
.appendTo(this).append($allArticles.slice(5));
$this.find('.hidden-articles .article').hide();
}
});
$('#show').on('click',function() {
var $hidden = $('.hidden-articles .article:hidden:lt(5)');
$hidden.show();
});​
UPDATE
// If one one element to search
var elem = '.section' ;
hideArticles(elem);
// If Multiple Elements on the page...
$('.section').each(function() {
hideArticles(this);
});
$('#show').on('click', function() {
var $hidden = $('.hidden-articles .article:hidden:lt(5)');
$hidden.show();
});
function hideArticles(elem) {
var $this = $(elem),
$allArticles = $this.find('.article');
if ($allArticles.length > 5) {
$('<div/>').addClass('hidden-articles')
.appendTo(this).append($allArticles.slice(5));
$this.find('.hidden-articles .article').hide();
}
}​
Check Fiddle

How to display search results onkeyup?

I have a search box that as the user types letters into the search box, we will filter and display the results. However, as the user types each letter the search results are getting toggled between showing and hiding. I am very new to JS so I hope it could be an easy fix.
Here is my HTML:
See Below
Here is my toggle JS:
See Below
How can I tweak the JS to not toggle back and forth?
//Here are my edits to help answer the question. This is the JS and HTML I am using to display the results:
HTML:
<div class="friendssearch" onclick="toggle_visibility('friendslist');">
<div class="friendssearch">
<div id="friendssearchbox"></div>
</div>
<ul id="friendslist" style="display: none;">
<li>
<a href="#">
<div class="friendsflybox" title="Friends Name">
<p class="friendsflyboxname">Ryan Bennett</p>
</div>
</a>
</li>
</ul>
</div>
Javascript:
<script>
(function ($) {
// custom css expression for a case-insensitive contains()
jQuery.expr[':'].Contains = function(a,i,m){
return (a.textContent || a.innerText ||
"").toUpperCase().indexOf(m[3].toUpperCase())>=0;
};
function listFilter(header, list) { // header is any element, list is an unordered list
// create and add the filter form to the header
var form = $("<form>").attr({"class":"filterform","action":"#"}),
input = $("<input>").attr({"class":"filterinput clearFieldBlurred
ClearField","type":"text", "value":"Start typing a Name"});
$(form).append(input).appendTo(header);
$(document).ready(function() {
$('.clearField').clearField();
});
$(input)
.change( function () {
var filter = $(this).val();
if(filter) {
// this finds all links in a list that contain the input,
// and hide the ones not containing the input while showing the ones that do
$(list).find("a:not(:Contains(" + filter + "))").parent().slideUp();
$(list).find("a:Contains(" + filter + ")").parent().slideDown();
} else {
$(list).find("li").slideDown();
}
return false;
})
.keyup( function () {
// fire the above change event after every letter
$(this).change();
});
}
//ondomready
$(function () {
listFilter($("#friendssearchbox"), $("#friendslist"));
});
}(jQuery));
</script>
You'll need to do something similar to the code I have posted below. This assumes that you can access the object that contains the search results.
function toggle_visibility(id)
{
//Check if there are any search results to display
var searchResultLength = document.getElementById(searchResultID).innerHTML.length;
if (searchResultLength > 0) // display div
{
var e = document.getElementById(id);
e.style.display = 'block';
}
else //No search results, hide div
{
e.style.display = 'none';
}
}
Basically, you need to determine if you have search results to display before you attempt to toggle the div's visibility.
//EDIT AFTER COMMENTS
OK, so it looks like the results are adding li's to the ul. So, assuming that the code is taking away the li's as well as adding them, you should be checking for the number of elements in the ul == 0. See below.
$('#friendslist > li').length
To be honest, I'm having a bit of difficulty trying to determine exaclty what the code is
doing. I'm certainly not a jquery expert. I would say if the above code doesn't get you going in the right direction, I'm out of ideas.
If you're only wanting it to display when you enter the field use the onFocus="method()" attribute. followed by onBlur="method()". this will display the block when you enter the field and hide it when you leave.
<input id="searchbox" type="text" onFocus="toggle_visibility('friendslist');" onBlur="toggle_visibility('friendslist')">
<ul id="friendslist" style="display: none;">
<!--search results HTML-->
</ul>
teach a man to fish: http://www.w3schools.com/jsref/dom_obj_event.asp
// EDIT
I think Quickfire's answer is the best solution. but as I understand it you want you results to show/hide, so I modified his method to better suit your markup.
function toggle_visibility(id){
//Get the total number of <li> within my search result
var results=document.getElementById(searchResultID).childNodes.length;
if (results > 0){ // we have more than 0 results
var e = document.getElementById(id);
e.style.display = 'block'; // show the element
}else{ //No search results, hide div
e.style.display = 'none'; //hide the element
}
}

Categories