I am trying to create a richtext area using contenteditable. The parser for the highlighting returns a html string which I replace the content of the element with (perhaps ineffective, thats the way it has to be for now).
The problem with contenteditable is that the cursor position is not retained after something like this, which I can understand.
Is there a way to retain this position? I could make the parser return a element representing where the caret should be (given the cursors offset in the text) if this would aid in a solution.
Thanks in advance.
I could make the parser return a element representing where the caret should be
That would be enough. Suppose you parse the generated HTML and now you have a reference to the caret-should-be-here element in sentinel.
Use document.createRange() to create a DOM range. Let here be the new range. Call here.selectNode(sentinel) to have the range surround the element.
Call here.deleteContents() to remove the dummy element.
Call window.getSelection().removeAllRanges() to clear the current selection, which has gotten messed up by the HTML change.
Call window.getSelection().addRange(here) to put the cursor where the element used to be.
is sentinel my anchor element?
Yeah, I guess.
How do I fetch the cursor position in the string? ... I want the offset from the start of the string.
Let's start with the cursor position. The zeroth range of the window's selection should be the cursor's position. That is, the range's start and end are in the same place, at the cursor. However, these locations are expressed in a way that's geared toward DOM trees, rather than for string and offsets. They have a (start|end)Container and a (start|end)Offset. Check the DOM spec for what these mean.
You're interested in some sort of string offset. As I interpret it, this would be like, if you took a plaintext-only version of the subtree, what index corresponds to the range? There are multiple ways to define a plaintext version of a subtree. One is what the textContent property returns, where foo<br>bar gives "foobar". Some browsers define innerText, where foo<br>bar gives "foor\nbar". You've probably picked the one that you'll be using, but it's not stated in the question.
Anyway, here's an idea, but it might not be the right kind of offset for your app.
Set the window selection to a single range going from the beginning (wherever index 0 should be) to the cursor position.
Read window.getSelection().toString().length. In the browsers I've developed for, toString on a selection object gives results comparable to innerText.
Related
I set up at test case on jsperf here: http://jsperf.com/rect-vs-offsettopmsa
Now a lot of the results there seem intuitive and make sense, however there is one thing that bothers me regarding ScrollTop and OffsetTop.
Why is it that ScrollTop is almost 3-4x faster than OffsetTop if they are both DOM Element properties? Especially since OffsetTop is also only a read-only property according to the msdn: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetTop
I can't say for sure — you'll have to examine source code to be sure. But the obvious reason for the offsetTop poorer performance is that it is simply more complicated to compute.
To calculate Element.scrollTop browser just needs to explore the Element you asking for — it can get all the input data it needs just from it.
But to calculate Element.offsetTop you need to calculate the element's position AND it's parent's — and then compare them to get the relative position. Hence more time needed to perform it.
Here you can find a description of how Element.offsetTop works according to spec.
If the element is the HTML body element or does not have any associated CSS layout box return zero and terminate this algorithm.
If the offsetParent of the element is null return the y-coordinate of the top border edge of the first CSS layout box associated with the element, relative to the initial containing block origin, ignoring any transforms that apply to the element and its ancestors, and terminate this algorithm.
Return the result of subtracting the y-coordinate of the top padding edge of the first CSS layout box associated with the offsetParent of the element from the y-coordinate of the top border edge of the first CSS layout box associated with the element, relative to the initial containing block origin, ignoring any transforms that apply to the element and its ancestors.
So yeah, even if it looks like a simple property, it still can trigger some calculations on the element.
UPDATED:
It looks that my answer is still incorrect because of this interface declared in the same spec:
partial interface HTMLElement {
readonly attribute Element? offsetParent;
readonly attribute long offsetTop;
readonly attribute long offsetLeft;
readonly attribute long offsetWidth;
readonly attribute long offsetHeight;
};
So, yes, both this properties are readOnly, so the fact that one is 3 times faster then other doesn't make much sense. Ignore what I've written earlier.
I have added visually hidden jump links to my website, that appear when they are focused (similar to e.g. GitHub, just press Tab once on the home page).
I want to test this behaviour with Capybara. I can't check for e.g. 'visibility: true/false', because the links are not really hidden (otherwise screenreaders would't see them), but only moved out from the viewport by absolute positioning. When they are focused, they are placed at their original position in the viewport.
So I guess I have to check the X and Y coordinate, but Capybara doesn't seem to offer a native method for getting them? Is this true? And if so, how would I get them for e.g. an element '#jump_to_content'? By executing some JavaScript?
Update
I found some inspiration here: https://content.pivotal.io/blog/testing-accessibility-with-rspec-and-capybara
But this doesn't seem to work for my configration:
link.native.location
NoMethodError: undefined method `location' for #<Capybara::Poltergeist::Node tag="a">
You are correct, Capybara does not offer a native method for getting an element's position in the page, but if you have access to jQuery you can use Capybara's page.evaluate_script method to easily determine an element's position. It would look something like this:
page.evaluate_script("$('.menu > div:nth-child(1)').offset().top")
The .offset() method allows us to retrieve the current position of an element relative to the document. Contrast this with .position(), which retrieves the current position relative to the offset parent
Just note that
While it is possible to get the coordinates of elements with visibility:hidden set, display:none is excluded from the rendering tree and thus has a position that is undefined.
Using capybara's page.evaluate_script will allow you to execute some JavaScript and create an assertion off the return value:
expect(page.evaluate_script("document.querySelectorAll('a#jump-to-content')[0].style.top;")).to eq('-8000px')
According to this SO answer, the best way to use JavaScript to find the position of an element is to use getBoundingClientRect(); as it returns values that are relative to the viewport.
It feels more readable to me to find the element with Capybara instead of JavaScript, and then find its position. I'm using Selenium and Chrome.
link = find("a", text: "content")
link.evaluate_script("this.getBoundingClientRect().left;")
link.evaluate_script("this.getBoundingClientRect().right;")
link.evaluate_script("this.getBoundingClientRect().top;")
link.evaluate_script("this.getBoundingClientRect().bottom;")
Some browsers (like Chrome) also have:
link.evaluate_script("this.getBoundingClientRect().x;")
link.evaluate_script("this.getBoundingClientRect().y;")
link.evaluate_script("this.getBoundingClientRect().height;")
link.evaluate_script("this.getBoundingClientRect().width;")
You can also do
link.rect.y
link.rect.height
link.rect.x
link.rect.width
As per Nuri's answer, the best way you can go is asking the browser directly(headless browsers don't read css files, afaik).
However, rather than asking the style of the element, you might want to directly check its position by using offsetTop. So something like
expect(page.evaluate_script("document.querySelector('a#jump-to-content').offsetTop;")).to eq('-8000px')
If you need more control, you could also run it all in javascript:
expect(page.evaluate_script("document.querySelector('a#jump-to-content').offsetTop < document.querySelector('body').offsetTop;").to be_true
I have an UIWebView with a huge book in it. I'm changing it's font size via javascript, using "document.getElementsByTagName('body')[0].style.webkitTextSizeAdjust='150%';
Html-page gets larger, but the scroll position remains the same, causing text to shift out of a users sight.
The only idea that I have, is really weird and inefficient:
Wrap every word in <span> tags;
find the first onscreen <span> and remember it's id;
resize font;
scroll to span, that I've found in step 2.
Is there a better way to preserve the position, that user was reading?
Finally I've found an acceptable way:
Before changing font size I use a little javascript to find and store a position of a first letter on a page:
var range = document.caretRangeFromPoint(0,0); // get a range of a first onscreen letter
var textContainer = range.startContainer.parentNode;// get an element to which it belongs
var path = getElementXPath(textContainer); // get an XPath for that element (this function is not biult in, but you can find it in some other question)
path+='|'+range.startOffset; // stick XPath and index of the letter together
After that I change the font size, find needed element by XPath, insert invisible <a> right before my letter, scroll to that invisible <a>, don't forget to remove it.
Done. That is not a stragihtforward idea, but at least it works and does not consume to much of CPU or RAM, like the idea that I'have explained in original question.
Here is the place to get getElementXPath() function
How can I get the current caret position in tinyMCE? Referring to this question "Get cursor position or number of line on which the cursor is in TinyMCE" there is mentioned how to get the line number in tinyMCE, however I can't find no reference on how to get the actual caret position? I need this since I am using tinyMCE as a document for real-time collaboration therefore I need to know the position of the caret for clients writing in the same document so that I broadcast the position on some events (keystroke, click, etc..) and paint custom carets on the position for one client to know where the other client is writing or editing.
The only solution I found was to add a span after the content, select it and remove it as follows:
<span id="caret_pos_holder"></span>
Then once inserted, do this...
ed.selection.select(ed.dom.select('span#caret_pos_holder')[0]); //select the span
ed.dom.remove(ed.dom.select('span#caret_pos_holder')[0]); //remove the span
Is this the only way around it?
EDIT: Actually the above only sets the position of the cursor to the end of the content, something which I already took care of using bookmarks.
Is it possible that there is nothing that can get the cursor's position?
var sel = ed.selection.getSel();
will return the current selection as a selection object. You can then query sel.anchorNode and sel.anchorOffset to get current caret position.
Broadcasting this data to the other client(s) would be easier than trying to extract the position as a number, but if that's really necessary, you could climb the DOM from the position given above.
I used to use cumulativeOffset() and getDimensions() to calculate the bounding box of elements, but just realized that cumulativeOffset returns the top left corner oft the start of an element. Meaning: if an inline element wraps, and the next line is more to the left than the start, I get a displaced bounding box.
After researching a bit, I found that I can use getClientRects() to get all rects. I could then go through, and just take the left position of the rect that's most to the left.
I was wondering if there is a better way of doing this... I just didn't find a boundingBox() prototype function. Did I overlook it?
Edit: I also just found out that getClientRects() is not supported by all browser, so this is no solution.
I don't see a boundingBox() function either, but I wonder if using the same technique (cumulativeOffset() and getDimensions()) on the parent via: getOffsetParent() would do what you want. getOffSetParent():
"Returns element’s closest positioned
ancestor. If none is found, the body
element is returned."
Which should account for word-wrapping where the second line is further left.
I've never heard of a way to do this. You could set it position:relative, drop a 1x1 absolutely positioned div into it, position it right:0, get that div's LEFT + WIDTH, and subtract the offset width of the original inline item from that value.
Saying that, total hack, maybe you need to rethink the reason you want to do this!
The solution given by dfitzhenry seems not working in the case of multiline inline elements.
Here's my Javascript solution : get your inline element nextSibling, check if it is an inline element, otherwise create a new inline element, add it to your inline element's parent and then get its offsetLeft:
var parentContainer = inline_elm.parentNode;
var nextsib = inline_elm.nextSibling;
var remove_next_sibling = false;
if(!nextsib || nextsib.clientWidth != 0){
remove_next_sibling = true;
var temp= document.createElement('span');
parentContainer.insertBefore(temp, nextsib);
nextsib = temp;
}
var inline_bounding_right = nextsib.offsetLeft;
if(remove_next_sibling) parentContainer.removeChild(nextsib);
This is an old post, but the standard method now to get a bounding box is getBoundingClientRect(), which is supported in all major browsers and has had at least partial support since 2009 in most browsers. Enjoy!
P.S. getClientRects() is also very useful for getting the individual bounding boxes of the wrapped text. It seems to have the same browser support as getBoundingClientRect(), since the one depends on the other, and this source suggests that it's well supported.