E.g. I have a div that contains all the text like:
<div class="text-container">
<p>
Though we welcome the idea of a smaller connector, we're miffed that Apple couldn't just adopt the semi-industry standard of Micro-USB. That would make things easier for smartphone users across the globe. Yet, even so, the smaller connector may be a smart move for the future. The 30-pin connector has been around since 2003, long before the iPhone even existed: frankly, it's a dust magnet. A smaller connector helps shave extra space to achieve a smaller phone with perhaps a bigger battery. The new connector cable will mainly be used for syncing and charging by most people who own an Apple TV or Bluetooth/AirPlay accessories.
</p>
</div>
And I would like to create something like this:
<div class="text-container">
<p>
Though we welcome the idea of a smaller connector...[show more]
</p>
</div>
I guess I should get all the content of the div and then Find the first e.g. 50 character and put there a link and all the other text put in some div which will be hidden, and after the click on the link the other stuff show up.
It should be toggle-like and change the text from [show more] to [show less] if it is expanded and vice versa.
Any advice how to achieve this with plain javascript and jquery itself and without other jQuery plugins?
Here is another solution.
It doesn't simply cut the words in the middle but checks endings, punctuation, and long words.
$(".text-container p").each(function() {
var val = $.trim(this.innerHTML),
parsed = val.split(/\s+/),
cut = parsed;
// for each word
for (var i = 0, k = 0; i < parsed.length; i++) {
k += parsed[i].length + 1;
if (k - 1 > 50) {
cut = parsed.slice(0, i);
break;
}
}
// if one long word
if (cut.length == 0) {
cut.push(parsed[0].substring(0, 50));
}
val = cut.join(" ");
// if the text is long enough to cut
if (cut.length != parsed.length) {
this.innerHTML = val.replace(/[.,;?!]$/, "")
+ "<span>...</span> ";
$("<span />")
.css("display", "none")
.html(parsed.slice(cut.length).join(" ") + " ")
.appendTo(this);
$("<a />", {
href : "#",
text : "[show more]"
}).on("click", function(e) {
var sh = this.innerHTML == "[show more]";
$(this).prev("span").toggle(sh).prev("span").toggle(!sh);
this.innerHTML = sh ? "[show less]" : "[show more]";
e.preventDefault();
}).appendTo(this);
} else {
this.innerHTML = val;
}
});
DEMO: http://jsfiddle.net/xRuch/
Build a quick demo for you
jQuery
$(function(){
var $el = $(".text-container").find("p");
var str = $el.text();
var str1 = str.substr(0,50), str2= str.substr(51);
$el.html(str1+" <span class='showMore'><a href='#'>Show more...</a></span><span class='moreText'>"+str2+"</span><span class='showLess'><a href='#'>Show Less</a></span>");
$(".showMore").on("click", function(){
$(this).next(".moreText").show();
$(this).next(".moreText").next(".showLess").show();
$(this).hide();
});
$(".showLess").on("click", function(){
$(this).prev(".moreText").hide();
$(this).prev(".moreText").prev(".showMore").show();
$(this).hide();
})
});
css
.moreText, .showLess { display:none};
on modern browsers you could use the css property text-overflow:ellipsis;
http://jsfiddle.net/Y8skB/1/
i used css in my example to expand the text, but you could also do it with jquery if you want a show more link.
Related
My demo in JS Fiddle https://jsfiddle.net/dineshkanivu/5fp2sjgb/2/
I want to add content Dynamically to the id="myNote" in its 4th line.
click the button lines , you can see total number of lines. i want to add some html content after 4th line. How can i do this using jQuery
Snippet :
$(function() {
$("#getLines").click(function() {
var myheight = $("#myNote").height();
parseFloat($("#myNote").css("line-height"));
//$('#myNote').after('<button>button</button>');
alert(myheight);
});
});
#myNote {
width: 300px;
line-height: 1;
height: auto;
text-align: justify;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="myNote">
Finally, designing the last sentence in this way has the added benefit of seamlessly moving the reader to the first paragraph of the body of the paper. In this way we can see that the basic introduction does not need to be much more than three or four
sentences in length. If yours is much longer you might want to consider editing it down a bit! Here, by way of example, is an introductory paragraph to an essay in response to the following question:
</div>
<button id="getLines">lines</button>
According to this post I wrote a little function to do this.
Surely there a more efficent way. But it works fine.
I wrap every word in an own span. After that I check the positions of all spans, get the line number and add a class with this line number to the span.
function insertTextAtLine(target,lineNumber,textInsert){
var words = target.text().split(' ');
var text = '';
$.each(words, function(i, w){
if($.trim(w)) text = text + '<span>' + w + '</span> ';
});
target.html(text);
var line = 0;
var prevTop = - parseFloat($("#myNote").css("line-height"));
$('span', target).each(function(){
var word = $(this);
var top = word.offset().top;
if(top != prevTop){
prevTop = top;
line++;
}
word.attr('class', 'line' + line);
});
var insert=$('<span />').text(textInsert);
target.find('.line'+lineNumber).first().prepend(insert);
};
Fiddle:https://jsfiddle.net/tye3czva/4/
I am not quite sure how to put my Question in the right words, but I try to describe what I want to do.
Let's say we have a set of data (eg. numbers) from a database and they need to be output as a list. I put a predefined amount of data-fields in a row (for dekstop systems). Every data is in a SPAN with color/margin/padding styles. (Like table-cells.) After that amount of Spans is reached, a forced line break is given. And with each line break, the background color for all spans in that "row" is altered (odd/even). So far no problem.
However, if now someone checks that page with a Smartphone (or you simple resize your Browser Window), the predefined amount does not fit anmyore. As example, in large resolutions you have 6 Spans side by side, colored odd, than 6 Spans colored even. In a small resolution you maybe only have 3 Spans side by side, however in my design you have still 6 Spans colored odd, so two "rows" with the same background-color before it is altered.
Example HTML Output:
<span class="odd">Number 01</span>
<span class="odd">Number 02</span>
<span class="odd">Number 03</span>
<span class="odd">Number 04</span>
<span class="odd">Number 05</span>
<span class="odd">Number 06</span>
<br/>
<span class="even">Number 07</span>
<span class="even">Number 08</span>
<span class="even">Number 09</span>
<span class="even">Number 10</span>
<span class="even">Number 11</span>
<span class="even">Number 12</span>
<br/>
I have absolutely no idea if it is possile to get to know - maybe through Javascript or CSS, how many spans are displayed in a "row", to automate the odd-even-coloring, make it in a way responsive.
Check my Fiddle to maybe show better what I'm trying to get.
Don't you want to use any css framework? Like Bootstrap or Foundation? I hope that will make your work more easier.
Please go through the link.
https://getbootstrap.com/examples/grid/
It already has the solution for mobile device and medium screen.
I can't think of any way to pull it off with CSS, but here's a javascript solution. Browser support is a little off. Didn't realize you tagged jQuery in your post, but it should work on modern browsers at least:
window.addEventListener('resize', function(){
var cells = document.querySelectorAll('.odd,.even');
var activeClass='even', activeLine = 0;
for(var i = 0, len = cells.length; i < len; i++) {
cells[i].classList.remove('odd');
cells[i].classList.remove('even');
if(activeLine != cells[i].offsetTop + cells[i].offsetHeight) {
activeClass = (activeClass === 'even') ? 'odd' : 'even';
activeLine = cells[i].offsetTop + cells[i].offsetHeight
}
cells[i].classList.add(activeClass);
}
});
/* edit: forgot to dispatch the event.
jQuery makes this so much easier to write
than the monstrosity below. */
var event;
event = document.createEvent("HTMLEvents");
event.initEvent("resize", true, true);
window.dispatchEvent(event);
Demo here: http://jsfiddle.net/xh0o6gvy/1
Edit: Here is the jQuery version that Sunny put together found in a fiddle from the comment below. It's definitely a better way to go than the above code if compatibility with older browser versions is required.
function colorSpans() {
var containers = $('.span_container');
containers.each(function() {
var activeClass = 'even';
var activeLine = 0;
var cells = $(this).children('.odd, .even');
cells.each(function() {
$(this).removeClass('odd even');
var offset = $(this).offset();
var height = $(this).outerHeight();
if(activeLine != offset.top + height) {
activeClass = (activeClass === 'even') ? 'odd' : 'even';
activeLine = offset.top + height;
}
$(this).addClass(activeClass);
});
});
}
$(window).on('resize', function() {
colorSpans();
});
colorSpans();
Thanks everybody for your help and inspirations!
So just to round it up, here is the final function I am now using that works with fixed and variable container sizes and recolors container by container.
function colorSpans() {
var containers = $('.span_container');
containers.each(function() {
var activeClass = 'even';
var activeLine = 0;
var cells = $(this).children('.odd, .even');
cells.each(function() {
$(this).removeClass('odd even');
var offset = $(this).offset();
var height = $(this).outerHeight();
if(activeLine != offset.top + height) {
activeClass = (activeClass === 'even') ? 'odd' : 'even';
activeLine = offset.top + height;
}
$(this).addClass(activeClass);
});
});
}
An here is the fiddle: http://jsfiddle.net/z9db7p0t/1/
You could accomplish this with Javascript or plain CSS depending on the rest of your HTML/CSS.
Are the width of the span elements fixed? What about their parent container? If so, you should know where the breakpoints in your layout exist and easily target the elements via css within the proper media queries.
If the size is dynamic, you could update the classes on the spans by calculating how many spans could fit in a row. This would need to be called each time the page was resized however. Using jQuery:
$(function() {
var container = $('#span-container'),
spans = container.find('span');
$(window).on('resize', function(evt) {
var containerWidth = container.width(),
spanWidth = spans.width();
var howManyPerRow = Math.floor(containerWidth / spanWidth);
//reset rows
spans.removeClass('odd even');
var row = 'odd',
c = 1;
spans.each(function() {
$(this).addClass( row );
if( c % howManyPerRow == 0 ) {
row = (row == 'odd') ? 'even' : 'odd';
}
c++;
});
});
});
You are going to need to remove the <br /> tags for this to work correctly. Also, you should set white-space: nowrap; on the spans IMO.
I have: Simple block of html text:
<p>
The future of manned space exploration and development of space depends critically on the
creation of a dramatically more proficient propulsion architecture for in-space transportation.
A very persuasive reason for investigating the applicability of nuclear power in rockets is the
vast energy density gain of nuclear fuel when compared to chemical combustion energy...
</p>
I want: wrap word into span when user click on it.
I.e. User clicked at manned word, than I should get
<p>
The future of <span class="touched">manned</span> space exploration and development of space depends critically on the
creation of a ....
Question: How to do that? Is there way more efficient that just wrap all words into span at loading stage?
P.S. I'm not interested in window.getSelection() because I want to imply some specific styling for touched words and also keep collection of touched words
Special for #DavidThomas: example where I get selected text, but do not know how to wrap it into span.
I were you, I'd wrap all words with <span> tags beforehand and just change the class on click. This might look like
$( 'p' ).html(function( _, html ) {
return html.split( /\s+/ ).reduce(function( c, n ) {
return c + '<span>' + n + ' </span>'
});
});
and then we could have a global handler, which listens for click events on <span> nodes
$( document.body ).on('click', 'span', function( event ) {
$( event.target ).addClass( 'touch' );
});
Example: http://jsfiddle.net/z54kehzp/
I modified #Jonast92 solution slightly, I like his approach also. It might even be better for huge data amounts. Only caveat there, you have to live with a doubleclick to select a word.
Example: http://jsfiddle.net/5D4d3/106/
I modified a previous answer to almost get what you're looking for, as demonstrated in this demo.
It finds the currently clicked word and wraps a span with that specific class around the string and replaced the content of the paragraph with a new content which's previously clicked word is replaced with the newly wrapped string.
It's limited a bit though because if you click on a substring of another word, let's say 'is' then it will attempt to replace the first instance of that string within the paragraph.
You can probably play around with it to achieve what you're looking for, but the main thing is to look around.
The modified code:
$(document).ready(function()
{
var p = $('p');
p.css({ cursor: 'pointer' });
p.dblclick(function(e) {
var org = p.html();
var range = window.getSelection() || document.getSelection() || document.selection.createRange();
var word = $.trim(range.toString());
if(word != '')
{
var newWord = "<span class='touched'>"+word+"</span>";
var replaced = org.replace(word, newWord);
$('p').html(replaced);
}
range.collapse();
e.stopPropagation();
});
});
Then again, #jAndy's answer looks very promising.
Your answers inspired me to the next solution:
$(document).ready(function()
{
var p = $('p');
p.css({ cursor: 'pointer' });
p.dblclick(function(e) {
debugger;
var html = p.html();
var range = window.getSelection() || document.getSelection() || document.selection.createRange();
var startPos = range.focusOffset; //Prob: isn't precise +- few symbols
var selectedWord = $.trim(range.toString());
var newHtml = html.substring(0, startPos) + '<span class=\"touched\">' + selectedWord + '</span>' + html.substring(startPos + selectedWord.length);
p.html(newHtml);
range.collapse(p);
e.stopPropagation();
});
});
We haven't there wrap each word in span. Instead we wrap word only on click.
use
range.surroundContents(node)
$('.your-div').unbind("dblclick").dblclick(function(e) {
e.preventDefault();
// unwrap .touched spans for each dblclick.
$(this).find('.touched').contents().unwrap();
var t = getWord();
if (t.startContainer.nodeName == '#text' && t.endContainer.nodeName == '#text') {
var newNode = document.createElement("span");
newNode.setAttribute('class', 'touched');
t.surroundContents(newNode);
}
e.stopPropagation();
});
function getWord() {
var txt = document.getSelection();
var txtRange = txt.getRangeAt(0);
return txtRange;
}
I'm using http://rockycode.com/blog/jquery-plugin-readmore/ for trim long text and add a "See more" link to reveal all the text.
I would love to avoid cutting words, how could I do that?
If the limit is 35, don't cut the w...
but
If the limit is 35, don't cut the word... (and in this case, trim it at 38 and then show the hidden text from 39th chtill the end.
Instead of doing this:
$elem.readmore({
substr_len: 35
});
You could do this
$elem.readmore({
substr_len: $elem.text().substr(0, 35).lastIndexOf(" ")
});
What we're doing is to go to the latest space posible before index 35.
Of course 35 can be variable. Also you could put it into a function to reuse it.
Hope this helps
You can change the abridge function within that plugin as follows:
function abridge(elem) {
var opts = elem.data("opts");
var txt = elem.html();
var len = opts.substr_len;
var dots = "<span>" + opts.ellipses + "</span>";
var charAtLen = txt.substr(len, 1);
while (len < txt.length && !/\s/.test(charAtLen)) {
len++;
charAtLen = txt.substr(len, 1);
}
var shown = txt.substring(0, len) + dots;
var hidden = '<span class="hidden" style="display:none;">' + txt.substring(len, txt.length) + '</span>';
elem.html(shown + hidden);
}
...and it will behave as you desire. You might want to add an option to turn this feature off and on, but I'll leave that up to you.
See working example →
I was just gathering information about this subject, with your help and the help from other related posts I wrote this:
http://jsfiddle.net/KHd6J/526/
I am using a 'contenteditable' <div/> and enabling PASTE.
It is amazing the amount of markup code that gets pasted in from a clipboard copy from Microsoft Word. I am battling this, and have gotten about 1/2 way there using Prototypes' stripTags() function (which unfortunately does not seem to enable me to keep some tags).
However, even after that, I wind up with a mind-blowing amount of unneeded markup code.
So my question is, is there some function (using JavaScript), or approach I can use that will clean up the majority of this unneeded markup?
Here is the function I wound up writing that does the job fairly well (as far as I can tell anyway).
I am certainly open for improvement suggestions if anyone has any. Thanks.
function cleanWordPaste( in_word_text ) {
var tmp = document.createElement("DIV");
tmp.innerHTML = in_word_text;
var newString = tmp.textContent||tmp.innerText;
// this next piece converts line breaks into break tags
// and removes the seemingly endless crap code
newString = newString.replace(/\n\n/g, "<br />").replace(/.*<!--.*-->/g,"");
// this next piece removes any break tags (up to 10) at beginning
for ( i=0; i<10; i++ ) {
if ( newString.substr(0,6)=="<br />" ) {
newString = newString.replace("<br />", "");
}
}
return newString;
}
Hope this is helpful to some of you.
You can either use the full CKEditor which cleans on paste, or look at the source.
I am using this:
$(body_doc).find('body').bind('paste',function(e){
var rte = $(this);
_activeRTEData = $(rte).html();
beginLen = $.trim($(rte).html()).length;
setTimeout(function(){
var text = $(rte).html();
var newLen = $.trim(text).length;
//identify the first char that changed to determine caret location
caret = 0;
for(i=0;i < newLen; i++){
if(_activeRTEData[i] != text[i]){
caret = i-1;
break;
}
}
var origText = text.slice(0,caret);
var newText = text.slice(caret, newLen - beginLen + caret + 4);
var tailText = text.slice(newLen - beginLen + caret + 4, newLen);
var newText = newText.replace(/(.*(?:endif-->))|([ ]?<[^>]*>[ ]?)|( )|([^}]*})/g,'');
newText = newText.replace(/[·]/g,'');
$(rte).html(origText + newText + tailText);
$(rte).contents().last().focus();
},100);
});
body_doc is the editable iframe, if you are using an editable div you could drop out the .find('body') part. Basically it detects a paste event, checks the location cleans the new text and then places the cleaned text back where it was pasted. (Sounds confusing... but it's not really as bad as it sounds.
The setTimeout is needed because you can't grab the text until it is actually pasted into the element, paste events fire as soon as the paste begins.
How about having a "paste as plain text" button which displays a <textarea>, allowing the user to paste the text in there? that way, all tags will be stripped for you. That's what I do with my CMS; I gave up trying to clean up Word's mess.
You can do it with regex
Remove head tag
Remove script tags
Remove styles tag
let clipboardData = event.clipboardData || window.clipboardData;
let pastedText = clipboardData.getData('text/html');
pastedText = pastedText.replace(/\<head[^>]*\>([^]*)\<\/head/g, '');
pastedText = pastedText.replace(/\<script[^>]*\>([^]*)\<\/script/g, '');
pastedText = pastedText.replace(/\<style[^>]*\>([^]*)\<\/style/g, '');
// pastedText = pastedText.replace(/<(?!(\/\s*)?(b|i|u)[>,\s])([^>])*>/g, '');
here the sample : https://stackblitz.com/edit/angular-u9vprc
I did something like that long ago, where i totally cleaned up the stuff in a rich text editor and converted font tags to styles, brs to p's, etc, to keep it consistant between browsers and prevent certain ugly things from getting in via paste. I took my recursive function and ripped out most of it except for the core logic, this might be a good starting point ("result" is an object that accumulates the result, which probably takes a second pass to convert to a string), if that is what you need:
var cleanDom = function(result, n) {
var nn = n.nodeName;
if(nn=="#text") {
var text = n.nodeValue;
}
else {
if(nn=="A" && n.href)
...;
else if(nn=="IMG" & n.src) {
....
}
else if(nn=="DIV") {
if(n.className=="indent")
...
}
else if(nn=="FONT") {
}
else if(nn=="BR") {
}
if(!UNSUPPORTED_ELEMENTS[nn]) {
if(n.childNodes.length > 0)
for(var i=0; i<n.childNodes.length; i++)
cleanDom(result, n.childNodes[i]);
}
}
}
This works great to remove any comments from HTML text, including those from Word:
function CleanWordPastedHTML(sTextHTML) {
var sStartComment = "<!--", sEndComment = "-->";
while (true) {
var iStart = sTextHTML.indexOf(sStartComment);
if (iStart == -1) break;
var iEnd = sTextHTML.indexOf(sEndComment, iStart);
if (iEnd == -1) break;
sTextHTML = sTextHTML.substring(0, iStart) + sTextHTML.substring(iEnd + sEndComment.length);
}
return sTextHTML;
}
Had a similar issue with line-breaks being counted as characters and I had to remove them.
$(document).ready(function(){
$(".section-overview textarea").bind({
paste : function(){
setTimeout(function(){
//textarea
var text = $(".section-overview textarea").val();
// look for any "\n" occurences and replace them
var newString = text.replace(/\n/g, '');
// print new string
$(".section-overview textarea").val(newString);
},100);
}
});
});
Could you paste to a hidden textarea, copy from same textarea, and paste to your target?
Hate to say it, but I eventually gave up making TinyMCE handle Word crap the way I want. Now I just have an email sent to me every time a user's input contains certain HTML (look for <span lang="en-US"> for example) and I correct it manually.