TinyMCE limit text on scrollerActived - javascript

I have a textarea in my rails application to collect content from user in a database. The rails application is further feeding that text to an XML-driven flex application.
The flex application has number of fixed sized containers which wraps the text inside (from the XML created by Rails app on-the-fly), but truncates the text if it exceeds the container's height. Problem is; there is no way to present the large text in XML, so it gets adjusted automatically in the compiled flex application. And the fact is; the web-based rails app and front-tier flex app are entirely disconnected in terms of having awareness of their internal events. (like in this case; rails app has no knowledge of the overflow event for flex internal containers and relying on font-size and character/line count doesn't work in this scenario!)
Therefore, I wrote a JS function to watch and rescue the textarea's overflow situation and while setting its attributes (viz; line-height, font-size, font-family, width, height... yada yada) matching that of the flex control. The complex form in rails did the trick to have dynamic number of such textarea's control being observed by the JS function.
Here is the Prototype code to handle the overflow event with the corresponding rescue code for cleanup:
var timeout;
document.observe('dom:loaded', attach_obr);
function attach_obr() {
$$('.active_text').each (function(text_element){
text_element.observe('keyup', function(e){
check_limits(text_element.id);
});
text_element.observe('change', function(e){
check_limits(text_element.id);
});
});
}
function check_limits(eyeD) {
if($(eyeD).scrollHeight > $(eyeD).offsetHeight){
// overflow occured, now the rescue code here
timeout = window.setTimeout(function() {
$("error_notice").hide();
}, 4000);
$("error_notice").show().update('There is no space left in this box, please use a new box to continue adding content');
// truncate text till the scrollbar disappears
while($(eyeD).scrollHeight > $(eyeD).offsetHeight){
$(eyeD).value = $(eyeD).value.slice(0, -1);
}
}
else {
if($("error_notice").innerHTML!=""){
$("error_notice").hide().update("");
clearTime(timeout);
}
}
}
[Note: It works with a minor flaw of truncating few more characters than expected in the last line. User can retype these letters till the end of that line. I guess this is because somehow the change in width of textarea due to the appearance of scroll-bar is effecting either the scrollHeight or offsetHeight during the process & there should be something more to the loop's condition ($(eyeD).scrollHeight > $(eyeD).offsetHeight)]
The while loop makes things bit slower, but at least it is serving the purpose. WYSIWYG is achieved. (I would love to hear any suggestion from the viewers to improve that inelegant code :O )
WYSIWYG is not achieved, in terms of rich/formatted text..
Incorporating Rich Text:
Rather than expecting from user to place tags inside the area , in the next phase, I am planning to deploy tinyMCE in my app. Now, to make the above function work with tinyMCE, I have the following code:
tinyMCE.init({
theme_advanced_buttons1 : "bold, italic, underline, strikethrough, separator, justifyleft, justifycenter, justifyright, justifyfull, separator, forecolor, backcolor",
theme:"advanced",
mode:"textareas",
plugins : "safari",
width: '360px',
height: '198px',
setup : function(ed) {
ed.onChange.add(function(ed, i) {
check_limits(ed.id);
});
}
});
The binding and firing of events is working alright. Unfortunately, the aim to control the text overflow is not working. Reason being;
a) ed.id is the id of my textarea not the interactive panel created by tinyMCE. So, the attributes like scrollHeight are offsetHeight are not getting changed for the hidden textarea control.
b) The value of textarea in this case also contains HTML code rather than the actual text. So, it is very implicit to tell what is the actual text without markup (which in our case is required when truncating the overflowed text).
My questions:
Is there a way to get the scrollHeight and offsetHeight of the control created by tinyMCE?
Is there a way to get the only-text version (without markup) of inner content of tinyMCE control?
(So, when I truncate the text in check_limits function, it doesn't effect/breaks the markup/DOM created by tinyMCE for the formatted text. In other words, I would be simulating the user action of pressing backspace on tinyMCE control in the while loop.)
Elegant way to do this whole exercise with & without tinyMCE?
Any suggestions are greatly appreciated!

First you need to know that tinymce creates a contenteditable iframe to let users edit html contents; contents from that iframe get written back to the textarea onSave. The textarea gets hidden in the rtinymce intiatilization process. The editor id is equal to the textarea id.
Here some suggestions:
1. Relevant code
var frameid = editor.id+'_ifr';
var currentiframe = document.getElementById(frameid);
var offsetHeight = currentiframe .contentDocument.body.offsetHeight;
var scrollHeight = currentfr.Document.body.scrollHeight
2. code for this (using jQuery)
var plain_text = $(editor.getBody()).text();
3. The only more efficient way to handle the while loop in the "without tinymce" case will be to slice off some more characters and follow a logarithmic approach. You slice off a bigger part of the string and then get to the final value in half-part paces. Example: You slice of 20 characters, but it fits. Then you slice off 10 characters of the original string. If it does not fit you try 15 characters and so on... this is more effectife then the while approach, but more complicated to develop.
EDIT:
It seems almost impossible to get the line number from the caret position. Problem here is that you do not know where the a text line breaks. Though it is easy to find out in which paragraph the cursor is located at (tinymce uses paragraphs to wrap text nodes).
There is a way to limit insertion in tinymce based on characters (i.e. limit can be set to 100 characters), but i guess this won't work for your use case unless you use a monospace font.
Another approach could be to set the tinymce css to set the editor window to the exact same width as your flex boxes (set the widht to the iframes body element should be sufficient). In this case it sould be easier to use the scrollHeigth approach - you would only need to find out if the heigth did change after insertion of text and then you could divied the heigth with the lineheigth to egt the line number. I suggest you write an own plugin to implement this. This is not that difficult. Here is a link to a tutorial for this.

Related

Restrict user input to fixed width and height contenteditable div

After a lot of research, I haven't found a post with exactly the same requirements so I thought write a new post.
I'm trying to create a fixed area (e.g. 200px by 300px) where the user can enter text input. He should be able to enter any character (including line breaks).
However, he should not be able to 'write outside the box' (i.e. there shouldn't be overflow scroll or hidden for the 200x300 area).
Once user reaches the 'bottom' of the area, they can't enter any more line breaks.
And once they reach the 'bottom right' corner of the 200x300 area, they shouldn't be able to enter any more characters at all.
Is this possible in css, angular, js, jquery, etc?
Limit the length of characters with base in font and div's size, but you must change the font size and family or line height because every browser can have different styles.
To limit the length of characters in the div is need to ignore the HTML tags in the content, like interpreting.
Firstly calculate how many characters fits there.
You can restrict the number of characters per line with the cols="" attribute and set the displayed the number of editable lines with the rows="" attribute. However limiting the number of rows could only be one with the maxlength attribute which would control the number of characters you can have, which you'd have to estimate. There are some hacks to limit the number of rows with event listeners, but they seem to have fairly major bugs.
It is possible, you just need to do following:
Use event handlers to control character input process. It is required to be able to stop processing further keystrokes when limit is reached. Use keypress and keydown, first handles character processing, second - control keys processing.
Each time user presses a key, use a separate buffer to produce final result, compute its bounding rectangle, and if it is bigger than limit, prevent event handling.
Height of text body could be calculated by multiplying number of lines by line height (interpret font-size and line-height CSS properties).
Width of text body could be computed rather easy with help of HTML5 canvas measureText method.
If you don't have canvas, you can use offscreen span (or any other inline) element - just fill innerHTML with text block and use its offsetWidth attribute. Actually, if you replace line break characters with <br>, you may use span approach to get both dimensions in one go - just make sure it has same style as editable container.
ContentEditable containers, as i remember, store text body in HTML format already (in other words - with <br>s instead of line break characters).

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

JavaScript: Truncating text based on outer object's dimensions

I want to, using JavaScript, chop the given text to fit the object in which the text resides and add "..." at the end.
For example:
JavaScript got data from 3rd party web service and needs to put the text into 200 x 300 px div. Text's length vary, so let's say it will take much more space than provided.
How to determine at which point text will break through the border and prevent that by chopping text and adding "..." at the end?
There are several jQuery plugins that can do this.
If you don't mind using the canvas element, it can be used to measure the width of the text. Here's an example:
http://uupaa-js-spinoff.googlecode.com/svn/trunk/uupaa-excanvas.js/demo/8_2_canvas_measureText.html
ruzee.com has a solution that uses prototype.js and a small bit of code [MIT licensed] to do what you want; demo
You may also want to look into the CSS 3 property text-overflow which also does this.
http://www.w3.org/TR/2001/WD-css3-text-20010517/#text-overflow-props
It's possible to check if the browser supports it, so you always can add a JavaScript fall back.
if (!'textOverflow' in document.createElement('div').style) {
// Use JavaScript solution here
}

How to truncate text until "more" link is clicked?

I have a long series of paragraphs and I'd like to trim each down to 2 lines (50 characters) and when you click a link "more" it will show the full paragraph.
I am using the prototype library and rails.
I'd ideally like to do this with out breaking the paragraph into 2 divs and showing the other when you click on more. Or is that the only way?
Put your text in a div and set the height to your desired height (with overflow: hidden). When the more link is clicked set the div height to div.scrollHeight . If you're using jquery or mootools you can throw in a neat transition.
<div id="myText" style="overflow:hidden; height:50px;">Text here...</div>
more
<script type="text/javascript">
function showMore() {
var mydiv = document.getElementById('myText');
mydiv.style.height = mydiv.scrollHeight;
}
// or with a transition (mootools)
function showMoreTransition() {
new Fx.Tween($('myText'), {
duration: 1000
}).start('height', $('myText').getScrollHeight());
}
</script>
Do you have a problem with spans? It seems the most effective way set this up is to wrap the excess in a hidden span tag. You can even wrap the whole operationin a nice helper method to make it reusable.
Assuming prototype:
def sample_with_more(body, html_options = {})
more_link = link_to_function(" More...", "$('more').hide(); $('hidden').show();', :id => 'more')
content_tag(:div, html_options) do
body[0..49] + more_link +
content_tag(:span, body[50..-1], :style => "display:none", :id => "hidden")
end
end
Because I'm a jQuery guy, here is some psuedo code
Select element which contains p
Select after first 50 chars and wrap a div around with a class 'more-text'
Insert with Js after a <button>more</button>
Add a click event button that sets display: block or something more fancy on the more-text
Remove button or change it's text to 'less' and change necessary code
The above answers assume that you send the full text to the browser, then let it only display a certain amount of it by clipping it vertically. This is actually a good idea, as truncating a text afer a certain amount of characters is actually not as straight-forward as it seems.
In an early project, I had a long list of truncated texts and didn't want to send them all to the browser in full length. The important thing to keep in mind here is if your text may contain control or escape characters (e.g. HTML, BBCode, HTML-Entities, etc) you need to take special care about them.
I ended up writing a small HTML-tag parser to not deliver HTML tags which were cut in half, and to add end-tags to e.g. bold, italic, etc, to not screw up the rest of the screen layout.
Additionally, it's usually not what you want - i.e. you won't get two lines worth of text for different screen widths or when having line break characters in your text.

Rendered pIxel width data for each character in a browser's font

I have a table column that needs to be limited to a certain width - say 100 pixels. At times the text in that column is wider than this and contains no spaces. For example:
a_really_long_string_of_text_like_this_with_no_line_breaks_makes_the_table_unhappy
I would like to calculate the width of text server-side and add an ellipsis after the correct number of characters. The problem is that I don't have data about the rendered size of the text.
For example, assuming the browser was Firefox 3 and the font was 12px Arial. What would be the width of the letter "a", the width of the letter "b", etc.?
Do you have data showing the pixel width of each character? Or a program to generate it?
I think a clever one-time javascript script could do the trick. But I don't want to spend time re-inventing the wheel if someone else has already done this. I am surely not the first person to come up against this problem.
How about overflow: scroll?
Ext JS has a module to do just that
TextMetrics
Provides precise pixel measurements
for blocks of text so that you can
determine exactly how high and wide,
in pixels, a given block of text will
be.
I am sure that there are other libraries available out there that do it as well.
This would not only be impossible to do server-side, it would also not make sense. You don't what browser your client will be using, and you don't know what font settings on the client side will override whatever styling information you assign to a piece of HTML. You might think that you're using absolute positioning pixels in your style properties, but the client could simply be ignoring those or using some plugin to zoom everything because the client uses a high-dpi screen.
Using fixed widths is generally a bad idea.
Very very hard to do server-side. You can never know what fonts users have installed, and there are many things that affect the display of text.
Try this instead:
table-layout: fixed;
That'll make sure the table is never larger than the size you specified.
Here is my client-side solution that I came up with. It is pretty specific to my application but I am sharing it here in case someone else comes across the same problem.
It works a bit more quickly than I had expected. And it assumes the contents of the cells are text only - any HTML will formatting will be erased in the shortening process.
It requires jQuery.
function fixFatColumns() {
$('table#MyTable td').each(function() {
var defined_width = $(this).attr('width');
if (defined_width) {
var actual_width = $(this).width();
var contents = $(this).html();
if (contents.length) {
var working_div = $('#ATempDiv');
if (working_div.is('*')) {
working_div.html(contents);
} else {
$('body').append('<div id="ATempDiv" style="position:absolute;top:-100px;left:-500px;font-size:13px;font-family:Arial">'+contents+'</div>');
working_div = $('#ATempDiv');
}
if (working_div.width() > defined_width) {
contents = working_div.text();
working_div.text(contents);
while (working_div.width() + 8 > defined_width) {
// shorten the contents of the columns
var working_text = working_div.text();
if (working_text.length > 1) working_text = working_text.substr(0,working_text.length-1);
working_div.text(working_text);
}
$(this).html(working_text+'...')
}
working_div.empty();
}
}
});
}
This is essentially impossible to do on the server side. In addition to the problem of people having different fonts installed, you also have kerning (the letter "f" will take up a different amount of space depending on what is next to it) and font rendering options (is cleartype on? "large fonts"?).
There's nothing you can do server-side to calculate it. All you have to work with is the browser identification string, which may or may not tell you the user's operating system and browser accurately. You can also "ask" (via a font tag or CSS) for a certain font to be used to display the text but there's no guarantee that the user has that font installed. Beyond that the user could have a different DPI setting at the operating system level, or could have made the text bigger or smaller with the browser zoom function, or could be using their own stylesheet altogether.
You could put the text into an invisible span and read that spans width, but basicly this looks like someone trying to sabotage your site, and therefore I would recommend banning posts with words longer than a certain lenth, for example 30 characters without spaces (allowing links to be longer !-)
-- but the simple approach is to put a block-element inside the table-cell:
<td><div style="width:100px;overflow:hidden">a_really_long_string_of_text_like_this_with_no_line_breaks_makes_the_ta ... </div></td>
This will effectively stop the table-cluttering !o]
If you're ok with this not working for FireFox, why not just use CSS? Have the table with table-layout:fixed, have the column in question have overflow:hidden;text-overflow:ellipsis; white-space:nowrap.
http://www.css3.info/preview/text-overflow/
This is a new function of css3.
Some users have larger or smaller default font settings. You can't do this on the server. You can only measure it once the browser has rendered the page.
Since font size can be easily changed on the browser side, your server-side calculation is made invalid very easily.
A quick client side fix would be to style your cells with an overflow attribute:
td
{
overflow: scroll; /* or overflow: hidden; etc. */
}
A better alternative is to truncate your strings server side and provide a simple javascript tooltip that can display the longer version. An "expand" button may also help that could display the result in an overlay div.
What you want is the <wbr> tag. This is a special HTML tag that tells the browser that it is acceptable to break a word here if a wrap is necessary. I would not inject the into the text for persistent storage because then you are coupling your data with where/how you will display that data. However, it is perfectly acceptable to inject the tags server side in code that is view-centric (like with a JSP tag or possibly in the controller). That's how I would do it. Just use some regular expression to find words that are longer than X characters and inject this tag every X characters into such words.
Update: I was doing some looking around and it looks like wbr is not supported on all browsers. Most notably, IE8. I haven't tested this myself though. Perhaps you could use overflow:hidden as a backup or something like that.

Categories