Calculating xy-position of text selection - javascript

I am trying to create my own text selection with DOM-elements. Yes, I mean the blue background you see behind the text when you select it in this element. The idea is to halt the default behavior (the blue color) and use my own elements to do the job by finding the xy-position of the selection and then placing absolute positioned elements. I want to be able to do this with a regular div.
I'm thinking I need 3 elements. One for the top row (which may be incomplete), one for the middle chunk, one for the last (same as top). Here's an image that helps you understand:
I've been thinking of catching mouseup/down and then mousemove and then check window.getSelection() but so far I'm having trouble getting anywhere.
Using the CSS ::selection will not work because the element will not have focus.
I appreciate all help I can get! Thanks in advance.
Edit:
Stumbled upon https://code.google.com/p/rangy/ which might be of help? Anyone with experience with this plugin?
Edit2:
Cross-browser support is required.

You can use getClientRnge:
var element = document.getElementById('element')
element.onmouseup = function(){
var selection = document.getSelection(),
range = selection.getRangeAt(0),
clientRects = range.getClientRects()
console.log(clientRects)
}
http://jsfiddle.net/XjHtG/
This will return the left, right, top, bottom, width and height of all selections made.

Related

jQuery scrolling out of view issue

I am working on building a schedule. So far it's pretty straight-forward. There is one bit of functionality I am having issues with.
I have an ul that has a fixed height to allow scrolling. There are "Labels" (li.dayLabel) withing the ul that separate the hours. What I am trying to do is to have it so that when a label is scrolled out of view it will change the text in the placeholder to it's text. Then once that works, I need it to work in reverse. So when they label scrolls back into view it updates the placeholder. Basically, I am trying to make the placeholder be a title for the available items until another label is then scrolled out of view. This is for scrolling from the top. So as you scroll down the list the placeholder is meant to be a title for the section you are viewing until you reach another section and it takes its place. Then when you scroll back down I need it to replace the text with the previous li.dayLabel so the sections stay organized. I hope this makes sense.
You can see what I am trying to do by looking at the original that I am basing this off of. Notice how the placeholder changes as you scroll down the list and changes back when you scroll back up.
Demo: jsFiddle // Note: line 54 is the part that is in question
I originally used:
$(".snlf-schedule-list li.dayLabel:visible:first").text();
as the :first selector is suppose to only match a single element.
I later tried:
$(".snlf-schedule-list li.dayLabel:visible").filter(":eq(0)")
as this is suppose to be the same thing.
It seems that when an element is out of view it still is considered :visible I believe this is my issue.
Am I doing this completely wrong? I was under the impression that when you scroll an element like this it should no longer be :visible. After reading the documentation I have learned that this is not the correct selector to use.
It would appear that scrollTop is how I should be doing this. Now I have used scrollTop for scrolling down pages to disable animations when not in view but I am not clear on how to untilize this for a ul with scrollbars.
I tried:
var _first = $('li.dayLabel:first'); // next element to scroll out of view?
if( $(this).scrollTop() > (_first.offset().top+_first.height())) {
// Out of view
console.log("out");
} else {
// in view
console.log("in");
}
Updated Demo: jsFiddle
But it seems to be redundant as it's already calculating the first element so I am not sure how to get the correct element (the next one that's about to scroll out of view.) Then I need this to work when they scroll back up...
Any insight on this is much appreciated. Hopefully it's something simple I am just over complicating or missing completely.
Thanks,
Jeremy
The solution for my case was:
// Set placeholder text on scroll
var _scrollCagePositionTop = $(".snlf-schedule-list").offset().top;
var _first = $('li.dayLabel:first'); // first dayLabel element
$(".snlf-schedule-list").scroll(function(){
var _lastOffText = $(_first).text();
$("li.dayLabel").each(function() {
if ($(this).offset().top < _scrollCagePositionTop) {
_lastOffText = $(this).text();
}
});
$("#schedule-placeholder").text(_lastOffText);
});
What I did was set the known position of the top of the scroll cage (_scrollCagePositionTop)
When the user scrolls I set a variable _lastOffText that keeps track of the last item text content when scrolled out of view (less offset top than the palceholder). I then set this value to the placeholder.
This method allows me to have the proper text in my placeholder when the user scrolls up or down.
To fix the issue of an empty placeholder when the user scrolls back to the top I just set the default of _lastOffText to be the text of the first label ($(_first).text())
Hope others find this useful.
Here is the working demo: jsFiddle Final

Repositioning after changing font size in UIWebView

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

Automatic Textarea cursor movement

My question is almost similar to Automatic newline in textarea in textarea but my context is more complex. I have multiple textareas which have a 1 row attribute thus giving an impression of writing on air(just meaning empty area on the website with no borders since the textarea border i have set in css as >>> border: 0;).
I would like to use JQuery or Javascript to calculate when the text entered by the user if he/she has reached the end of the row of the textarea so as to automatically move to the next textarea below. One way to solve this would be to try and count the characters but different characters have different widths.
I'm thinking of inserting a hidden element at the edge of the textareas to act as a trigger for the nexttextarea.focus() event but i have no idea of how i can achieve this.
I have tried goofing around and thinking of different hacks but only one seems to be the solution... Try to store each character in an array and give them their default space taking value in px...like 'a'=>0.7px,'b'=>0.9px or something of the sort if their is a list somewhere (although it looks like it would take a lot of overhead in terms of memory as i would have to store both capital, small letters and many other characters.) Then do some calculations depending on the width of the browser if it has been re-sized or if not the full size to determine when the textarea row width becomes full at the browser width edge. (For the time being the textarea width is 100% and has no parent therefore fills the whole browser width).
If anybody has an idea of a complex or simple way i can accomplish this, help me. A big problem is that IE and Mozilla introduce scroll-bars if the browser window is re-sized, and scroll-bars is what i never want the user to see(remember impression of typing into thin air).
Forgive me for being so verbose..just wanted to be accurate and detailed.
This is harder than it looks. Try checking the scrollHeight and scrollWidth of the textarea; if they changed, the text overflowed. At that point, you can move text from the end of the current textarea to the beginning of the next textarea until the scroll height/width goes away.
Here's an example:
document.onkeyup = function(evt) {
var event = evt || window.event;
var target = event.target;
var nextArea = target.nextSibling; // assumes no whitespace between textareas.
var chars;
if (target.tagName == 'TEXTAREA' && (target.scrollLeft || target.scrollTop)) {
chars = 0;
while (target.scrollLeft || target.scrollTop) {
target.value = target.value.replace(/.$/, function(m0) {
nextArea.value = m0 + nextArea.value;
return '';
})
++chars;
target.selectionStart = target.value.length;
}
nextArea.focus();
nextArea.selectionStart = chars;
}
}​
http://jsfiddle.net/L73RG/3/
Note that a fully-working solution will need to bind this to more than just keyup events, because the user can paste with the context menu, for example. You may want to bind it to some mouse events or even run it occasionally on a timer if you find the box can be modified without triggering events (e.g. through a top level "edit" menu).
Check on every keypress if the content of your textarea is overflowing with the answer provided to this question: Determine if an HTML element's content overflows
You'll then have to remove the extra characters and put them into the next textarea. Probably one by one, using your check overflow function after each character is moved to see if the text area is still overflowing.
A possible solution is to measure the size of the text. Ext-JS does it by creating an offscreen absolutely positioned div, giving it style properties for the font you want to measure, and them measuring the div itself.
You can use Ext-JS's code as inspiration http://docs.sencha.com/ext-js/4-1/#!/api/Ext.util.TextMetrics

"Disabling" an HTML table with Javascript

I've seen this done in a lot of sites recently, but can't seem to track one down. Essentially I want to "disable" an entire panel (that's in the form on an HTML table) when a button is clicked.
By disable I mean I don't want the form elements within the table to be usable and I want the table to sort of fade out.
I've been able to accomplish this by putting a "veil" over the table with an absolutely positioned div that has a white background with a low opacity (so you can see the table behind it, but can't click anything because the div is in front of it). This also adds the faded effect that I want. However, when I set the height of the veil to 100% it only goes to the size of my screen (not including the scrolling), so if a user scrolls up or down, they see the edge of the veil and that's not pretty.
I'm assuming this is typically done in a different fashion. Does anyone have some suggestions as a better way to accomplish this?
You could try javascript like:
function disable(table_id)
{
var inputs=document.getElementById(table_id).getElementsByTagName('input');
for(var i=0; i<inputs.length; ++i)
inputs[i].disabled=true;
}
Try the below with Jquery
$("#freez").click(function(){
$('#tbl1').find('input, textarea, button, select').attr('disabled','disabled');
});
$("#unfreez").click(function(){
$('#tbl1').find('input, textarea, button, select').removeAttr("disabled");
});
Disabling the inner elements of an HTML table can also be done using pointer-events CSS style as shown below:
table[disabled], table[disabled] input { pointer-events: none }
At any desired point in our JavaScript code, we can add disabled attribute to the parent table which will bring the CSS styling into effect:
let gameTable = document.getElementById('gameBoard');
gameTable.setAttribute('disabled', true);
Another way to do it would be using the opacity property.
function disablePanel(id) {
var panel = document.getElementById(id);
var inputs = panel.querySelectorAll('input, button'); //anything else can go in here
for (var i=0; i<inputs.length; i++) {
inputs[i].disabled = true;
}
panel.style.opacity = 0.3; //or any other value
}
Can't you just find out the height of the area in pixels with JavaScript? And then set the veil's height to that number?
I don't have the exact code in my head but offsetHeight might do the trick
Somebody please correct me if I am wrong, but I have seen Javascript and some derivate Javascript libraries that have a lot of options for accomplishing for what you would like to do. I have used the jQuery library to do some similar effects.
One thing to think about is what exactly you are trying to disable. Essentially tables are not interactive so disabling a table would not accomplish much. If it is the form elements within the table you want to disable. You can accomplish this using JavaScript.
Along with using JavaScript for disabling the form elements, you can also use it to change properties of the non interactive elements.
An example of this would be using JavaScript to change the color of the font and borders and other non interactive elements in the table to give the "look" of being disabled. Of course you still need to use JavaScript to disable the form elements.

How to get the bounding box of a wrapping inline element with prototype

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.

Categories