JavaScript - How to get the current selection 'index' within a document - javascript

My code here returns a JavaScript selection object from within an iFrame (the iFrame page is within the same domain, so no xss issue).
What I need to know is the index of the selection within the raw html code (not the dom).
UPDATE:
E.g.
If you have an html doc:
<html><body>ABC</body></html>
And in the UI, the user uses their mouse to select the text 'ABC', I want to be able to use JavaScript to determine the postion of the selected text in the html source. In this case the index of ABC is 13.
UPDATE 2
The reason I'm persisting with this madness, is that I need to create a tool that can revisit a page and pull text based on a selected text the user has identified at an earlier time. The user tells the system where the text is, and the system from that point on uses regular expressions to pull the text. Now, if the dom is not the same as the raw html, and there's no way to pinpoint the selection in the raw html - it's really difficult to know what reg ex to generate. I don't think there's another way around this.
// Returns the raw selection object currently
// selected in the UI
function getCurrentSelection() {
var selection = null;
var iFrame = document.getElementById('uc_iFrameGetPriceData');
try {
if (window.getSelection) { // Gecko
selection = iFrame.contentWindow.getSelection();
}
else { // IE
var iframeDoc = iFrame.contentWindow.document;
if (iframeDoc.selection) {
selection = iframeDoc.selection;
}
else {
selection = iframeDoc.contentWindow.getSelection();
}
}
}
catch (err) {
alert( 'Error: getCurrentSelection() - ' + err.description )
}
return selection;
}

You can access the index and offset of your selection by using selection.anchorOffset and selection.focusOffset.
Take a look at this:
http://help.dottoro.com/ljjmnrqr.php
And here's another well explaned article:
http://www.quirksmode.org/dom/range_intro.html
update to your update: I'm not sure why you're trying to get the index of the raw HTML code. But you can walk the DOM based on the selection kinda like this:
selection.anchorNode.nodeValue.replace(selection.anchorNode.nodeValue.substring(selection.anchorOffset, selection.focusOffset), 'replace value')
Note that it's still possible that anchorOffset is before focusOffset, based on whether you selected the text from left to right or from right to left.

If I understand correctly, you're looking to move around in the DOM. In that case, you can use these methods/properties:
parentNode
getChildNodes()
firstChild and lastChild
...and these links might help:
http://www.codingforums.com/archive/index.php/t-81035.html
http://www.sitepoint.com/forums/showthread.php?t=586034
The fastest way is probably
var node = document.getElementById('myElement');
alert(node.parentNode.indexOf(node));
(Sorry, for some reason the formatting buttons aren't showing up in my "Your Answer" area...)

I would be surprised if that information was available.
No DOM API is going to let you distinguish between
<html><body>ABC</body></html>
and
<html ><body >ABC</body></html>
The index in the raw HTML is different in each case, but the constructed DOM is identical.

You can't do this sensibly: the only possible method is to re-download the page's HTML via Ajax, parse the HTML and match the resulting DOM against the current DOM, which may itself have been altered by JavaScript. Besides, it's not a useful number anyway because once the page has been loaded, the original HTML string simply no longer exists in the DOM so offsets within that string have no meaning in JavaScript. Getting the selection in terms of nodes and offsets is much more sensible.

Related

Remove all elements from website except X

I'm not really familiar with Javascript, and even less with how Javascript works in Chrome's F12 developer tools. What I'm trying to do is have a favorite which, when clicked on, loads a web page but removes some of the clutter of the page which is loaded (I don't really care if it removes it before the page is loaded, or loads it and then removes it)
For now, I'm trying to figure out how to remove all elements except the one I want to keep (and its' children), namely, one which has the following html:
<div>
<ul class="c-list-news u-relative" data-load-more-content>...</ul>
</div>
I'm trying the following (from what I could find on SO), but I can't find the right selector (or I'm doing something else wrong, not quite sure):
var elem = document.querySelectorAll('body *:not(div ul.c-list-news, div ul.c-list-news *)');
for(var i=0;i<elem.length;i++) {
elem[i].parentElement.removeChild(elem[i]);
}
(PS : I haven't yet looked into how to put it into a favorite/extension, it will come later)
It's probably easier than you realize. :-) You can get the first element matching .c-list-news like this:
const cListNews = document.querySelector(".c-list-news");
If you want to keep its parent, just add .parentNode to that:
const divContainer = document.querySelector(".c-list-news").parentNode;
Then, wipe out body entirely:
document.body.innerHTML = "";
...and put the element back:
document.body.appendChild(cListNews); // Or `divContainer`
I'm not sure I'd expect the page to continue to be readable, though, since of course this completely changes where the element is in the DOM, which may well make the CSS fail.
You can't make a bookmark (favorite) that both loads the page and does this in one go, because javascript: bookmarks work within the context of the current page. You could use something like TamperMonkey which is an extension that lets you run a script automatically when you go to matching URLs.
But you can make a bookmark that you use when you're already on the page: Just use the javascript: pseudo-protocol and follow it with JavaScript code. For instance:
javascript:var divContainer %3D document.querySelector(".c-list-news").parentNode%3Bdocument.body.innerHTML %3D ""%3Bdocument.body.appendChild(divContainer)%3Bconsole.log("done")%3B
I created that by simply removing line breaks from the code (optional), running the code through encodeURIComponent, and putting javascript: on the front. (Some folks would also convert spaces to %20.)
Save the element to keep to a variable. Remove all nodes from the body, or the element that you want, and add the element to keep. Example:
let elementToKeep = document.getElementById('side');
const myNode = document.getElementsByTagName("body")[0];
while (myNode.firstChild) {
myNode.removeChild(myNode.firstChild);
}
myNode.appendChild(elementToKeep);
Using the removeChild method is faster that setting the innerHtml as empty string.
Check here: Remove all child elements of a DOM node in JavaScript

Send a Google Analytics event when a specific text string is present on a page

I have somewhere on website a specific text, let's say "NewYork", and I want to fire a Google Analytics event to track all the occurrences of this string.
For example if a visitor come to a webpage that contain the string 'NewYork', I want to send a Google Analytics event.
Text string is in a span tag as <span class="city">NewYork</span>
I do not know any JavaScript codes, just tried the following code adapted from someone else. And it is not working at all.
<script>
var htmlString = $('body').html().toString();
var index = htmlString.indexOf("NewYork");
if (index != -1)
{ ga('send', 'event', 'yesNewYork', 'foundnewyork'); } </script>
Does anybody know how to do this?
Possibly a jQuery solution?
Your approach is correct in principle, but can be improved upon.
You are already using jQuery syntax so I just assume jQuery is available.
First I would suggest you follow Lars Graubner suggestion and select a more specific element and grab the text content instead of the html (as the name suggests the text()-function does not return HTML markup but text only).
$('.city').text()
will adress the span from your example - the dot in the selector says "Look for elements with a classname of".
This will actually return all elements that have the class, but for your use case that does not matter much.
However you must make sure that the text is actually rendered on the page before you call your jQuery selector; if you place it in the head of the page the text isn't there yet when the event tracking runs and thus your selector will return nothing.
You can either put the script in the footer of the page, or you can wrap it into jQuery's document.ready-call. This makes sure that the function only runs after the DOM of the document has rendered (meaning that the page structure is complete, event if images and other assets are not yet loaded. Text will be present at this point). So this would look like this:
$( document ).ready(function() {
var myString = $('.city').text();
if(myString.indexof('NewYork') > -1) {
ga('send', 'event', 'yesNewYork', 'foundnewyork');
}
});
(Obviously this assumes you have jQuery included).
If this still doesn't work you need to be more specific as to the actual error your are getting.

Linking XML elements to page items in InDesign

I can check the XML of selected text like this:
app.selection[0].associatedXMLElements[0];
But in my research, I am still left scratching my head about how to do the most basic thing with XML using script: how do I assign XML to items? I can manually do this by opening the structure pane, then dragging the element over the desired frame on the page. If it's possible the old fashioned way, I imagine it's possible with script.
How do I link an existing XML element to an existing page item?
The above code only seems to work on selected text. If I select a graphic, it won't run.
How can I link XML to a selected graphic?
You can reference your xml node and your text frame and use placeXML
myXMl = myDoc.xmlElements[0];
var myXmlNode = myXMl.evaluateXPathExpression("/myXML/node1")[0];
var myFrame = app.activeDocument.pages[0].textFrames[0];
myXmlNode.placeXML(myFrame);
The advantage of this approach is that any aid:pstyle or aid:cstyle will be linked to existing matching style automaticaly
The alternative is to select the value of the node as text and place it into the text frame at insertion point:
myXMl = myDoc.xmlElements[0];
var myText = myXMl.xpath("/myXML/node1[1]/text()");
var myFrame = app.activeDocument.pages[0].textFrames[0];
myFrame.parentStory.insertionPoints[-1].contents = myText + '\r';
there are two specific properties. AssociatedXMLElement is for pageItems including textFrames and may be null if no tag is applied. AssociatedXMLElements only applies to text objects (characters, words…) because they can have several tags applied. Note that a non tagged text return an empty array and not null.
Associating tags to pageItems require that you first create or target existing xmlElements then use myInDesignObject.markup ( myXMLElement ).
EvaluateXPathExpression as Nicolai suggested is interesting once you want to browse through your XML structure. But it's sometimes quicker indeed to investigate associated XMLElement from the object rather than investigating the xml structure.
FWIW

Listen to specific changes on contenteditable?

Warning: not duplicate with existing questions, read through
I know I can have an event listen on changes on an contenteditable element.
What I would like is to be able to know what the changes are.
For example:
inserted "This is a sentence." at position X.
deleted from position X to Y.
formatted from X to Y with <strong>
Is that possible? (other than by doing a diff I mean)
The reason for this is to make a WYSIWYG editor of other languages than HTML, for example Markdown.
So I'd like to apply the changes to the Markdown source (instead of having to go from HTML to Markdown).
You may be able to do something with MutationObservers (falling back to DOM Mutation events in older browsers, although IE <= 8 supports neither) but I suspect it will still be hard work to achieve what you want.
Here's a simple example using MutationObservers:
http://jsfiddle.net/timdown/4n2Gz/
Sorry, but there is no way to find out what the changes are without doing a diff between the original content and the modified one when changes occur.
Are you looking for this
var strong=document.createElement("strong");
var range=window.getSelection().toString().getRangeAt(0);
range.surroundContents(strong);
this was for third part
You just need to select what you want to surround using real User interaction.
If you wanna do it dynamically
var range=document.createRange();
range.setStart(parentNode[textNode],index to start[X])
range.setEnd(parentNode[textNode],index to end[Y])
range.surroundContents(strong);
For 2nd Part
range.deleteContents()
1st part can be done by using simple iteration
var textnode=// node of the Element you are working with
textnode.splitText(offset)
offset- position about which text node splitting takes place[here==X]
Two child Nodes have been created of the parent editable Element
Now use simple insertBefore() on parent editable Element Node.
hope you will find it useful
The API you're looking for does not exist, as DOM nodes do not store their previous states.
The data / events you're wishing to get back are not native implementations in any browser Ive come across, and I struggle to think of a datatype that would be able to generically handle all those cases. perhaps something like this:
function getChanges() {
/* do stuff here to analyse changes */
var change = {
changeType : 'contentAdded',
changeStart : 50, /* beginning character */
changeContent : 'This is a sentence'
}
return change;
}
Since you're trying to get custom events / data, you're probably going to need a custom module or micro-library. Either way, to look at the changes of something, you need somehow be aware of what has changed, which can only be done by comparing what it was to what it is now.

Iterating over all tags in a string containing HTML using javascript/jquery

I'm using a rich text editor type control, which is a written as a jQuery plugin. It basically inserts an IFrame onto the page, and makes it editable - fairly standard for rich text controls.
Now, what I'm looking to do is improve upon an option which removes all formatting from the text editor. Currently it is being done with a large list of regular expressions, and a quick google search suggests that this is not the correct way to go about it. I'm looking to allow this unformatting some degree of flexibility, so that I can leave certain tags in (like paragraph tags).
I was trying to use the jQuery built in DOM parsing to do this easily, but I seem to be having trouble.
Let's assume I have a sample HTML string:
<Body><p>One <strong>Two</strong> <em>Three</em></p></Body>
I'm looking to un-format it so that all non paragraph tags are removed. So, I'd be expecting the output to be a string which looks like this:
<Body><p>One Two Three</p></Body>
Sample code:
//Some very simple HTML obtained from an editable iframe
var text = '<Body><p>One <strong>Two</strong> <em>Three</em></p></Body>';
var $text = $(text);
//All tags which are not paragraphs
$(':not(p)',$text).each(function() {
//Replace the tag + content with just content
$(this).html($(this).text());
});
//I'll be honest, I found this snippet somewhere else on stackoverflow,
//It seems to parse the jquery object back into an HTML string.
var returnVal = "";
$text.each(function(){
returnVal += $(this).clone().wrap('<p>').parent().html();
});
//Should be equal to '<p>One Two Three</p>'
return returnVal;
This seems like it should work, but unfortunately it doesn't. In the above example, 'returnVal' is the same as the input (minus the 'body' header tags). Is there anything I'm obviously doing wrong here?
Replace this line:
$(this).html($(this).text());
... with this:
$(this).replaceWith($(this).text());
... and it should work (at least it works here).
...snip
// Here's your bug:
$(':not(p)',$text).each(function() {
// You can't use .html() to replace the content
// $(this).html($(this).text());
// You have to replace the entire element, not just its contents:
$(this).replaceWith($(this).text());
});
...snip

Categories

Resources