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

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.

Related

How do I stop cursor moving to the start every time innerHTML is changed?

I have a function that separates words into separate span tags and updates the div I am typing into but every time I update the innerHTML the cursor moves to the start of the box so the next character I type outputs behind the last one instead of after it. Could someone tell me how I can stop the cursor from doing this?
Here is my code
const editorDiv = document.getElementById('editor');
function wrapWords(str, tmpl) { //separates words into <span>
return str.replace(/\w+/g, tmpl || "<span>$&</span>");
}
editorDiv.addEventListener('keydown', function(event) {
editorDiv.innerHTML = wrapWords(editorDiv.innerText)
});
<div id="editor" contenteditable="true">
<span>hello</span>
</div>
When I type into the div text looks like this:
.siht ekil skool txeT
One way I have solved this in the past is to use 2 seperate elements, positioned on top of each other, with position: absolute, so that they overlap in a pixel-perfect manner.
The element on top is a regular input element, and its text color is set to transparent.
The element behind it contains the styled text (spans etc.)
This way, you can replace the styled content as the user types, without interfering with the input element's cursor at all.
Sorry that I don't have an 'essential code snippet' at hand, but I hope you get the idea. It did work well in practice for me.

Treat div as a nonexistent element in HTML

Is adding a display:inline all that is needed for the browser display to treat the <div> as a nonexistent element (do want to consider everything inside the div though) in HTML?
I was thinking of having this div simply as a placeholder to put content into it from javascript and I was wondering whether it would be a good idea to make it display:inline
NOTE By nonexistent I mean that if the user says he wants to display the following on the page
<something here>
<something else here />
....
</something here>
Then the end result on the UI would be exactly what he wanted. Putting a div around it currently is adding a newline between this and other things.
I add this divs around something the user (the user being the programmer that is using the functionality I write) outputs in a function. I want to keep this divs completely invisible to the user. Currently there is a new line injected at times. For example there is a newline in between the two buttons
<div>
<button>Something</button>
<div>
<button>Else</button>
</div>
</div>
As long as you haven't styled the div with any width, height, margin, or padding you can leave it as is. No need to add "display: inline;". It's natural display: block; is just fine and won't take up any space as long as it is empty.
Then, if you inject content with, say, javascript the div will grow to fit the inside content.
Apparently a div has some display properties by default in the browser. Using a tag like <placeholder> seems like a good alternative that does not affect the UI at all.

Using Javascript or CSS, how can I add additional space at the end of a block of text, but only if it does not start on a new line?

Let's say I have some text, with a (more)... link at the end of each paragraph. It ends up looking something like this:
This is just a test paragraph. Click on the more link for more info. (more...)
Now I'd like to add some space between the link and the paragraph text, so I wrap the link in a <span> tag and push it to the right with a margin-left: 10px:
This is just a test paragraph. Click on the more link for more info. (more...)
The problem here is, if the text wraps right on the more so that it shows up on a second line by itself, it will look indented:
This is just a test paragraph. Click on the more link for more info. Push it down!
(more...)
When the desired output should be:
This is just a test paragraph. Click on the more link for more info. Push it down!
(more...)
Is it possible to achieve this effect using either Javascript or CSS?
Side note: It is possible to achieve this effect by removing the <span> tag and using &nbspcharacters to push the more linke to the right with proper text wrapping, but this is a solution I'd like to avoid unless there is no other choice. Or I could use Javascript to insert the &nbsp's before the span, but that's not exactly a nice solution either.
You could set a width on the paragraph and then float the span to the right.
That way the (more...) remains on the the right always.
Not exactly what you are after but I think it looks decent.
Example: http://jsfiddle.net/WFuBd/1/
If you wrap the content before the link in a span and apply a margin-right to that, you'll get the desired effect. (Unfortunately, this, too, is not really a nice solution)
Why not use 2 div's. Div one floats left, div two floats right... Make sure overflow=hidden on div one, and (more...) is on div two.
[div 1 lots of text i.e.: 300px] [div 2(more...) i.e.: 40px]
It will look perfect every time. You'll have to play around with it to look right, but it'll work. You could then just do a little jQuery when you click more...to show the rest and hide 'more'.

TinyMCE limit text on scrollerActived

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.

CSS Page Layout w/ Breaks

I'm trying to make a webpage where it basically looks like a word document. There would be multiple boxes that would scroll down and the text would flow and page break from one page to the next.
Does anyone have any idea where I would even start? Thanks.
Edit: It should be right in the browser, looking similar to this:
(Ignore the columns)
CSS mostly applies styles to a full element due to its box model. Exceptions are pseudo elements. So to create an appropriate break after a fixed length you would have to separate your text into correctly sized different elements.
EDIT:
It would be possible using javascript. But even in the simplest case, where everything inside the pages delivered as just one text element with no sub elements (not even other text elements), the code will be a development nightmare and will run quite crappy. This is because there is no measure function in javascript. So you would be forced to do trail and error to find the correct position to break the element. Since the properties of the elements are live it means, that the viewer of the website will see a lot of flickering of your page just after loading. If you dare put other elements inside the html element to break into pages you get even more problems. More or less you get hundreds of special cases (break inside other elements, what if those elements are inside even other elements) to look out for.
Something like that sounds possible using javascript, but it depends a bit on the structure of your html and whether or not you want to break paragraphs or just move the next paragraph to the next page if it doesn´t fit
So the simplest example, not breaking paragraphs / html elements with a flat html structure (no nested divs, columns, etc) like:
<div class="document">
<h1>title</h1>
<p>texts</p>
<h2>subtitle</h2>
<p>texts</p>
...
<p>texts</p>
</div>
would be to do something like:
height = 0
loop through all direct child elements of .document
{
if ( (height + element_height) > page_height)
{
add page_break_element before current element
height = 0
}
height = height + element_height
}
I´d use jquery because it makes it easy to loop through the elements, measure heights, etc.
I guess breaking paragraphs would be possible as well, but a lot of extra work.
<p style="page-break-before: always">This would print on the next page</p>

Categories