I have a content oriented product. And at one point I display a list of available entities, with their full summary. Now, the said summary is the data entered by user using TinyMCE editor (i.e. it can contain HTML tags like img, p, span, ul, li, etc.). As summary can span a few hundred lines, I want to cleanly implement the Show More-Less feature, using javascript where I load the summary hidden partially by default, and show rest only when user click, 'Show More'.
It would be great to know, how you guys have or would have implemented it. I am thinking to limit the variety of markup entered by user and use regex to split the markup with a span link to load-more (much like facebook does it).
Note: I cannot split the text according to number of characters/words, as it can violate markup. I cannot hide the content by limiting height due to img tags (which loads later and can alter the height of containing div, and in-turn spoil your height calculations.)
http://henrik.nyh.se/2008/02/jquery-html-truncate A very neat implementation by this guy.
You can also see working demonstration here http://henrik.nyh.se/examples/truncator/
I would use the following function to get rid of unwanted html tags.
// Strips HTML and PHP tags from a string
// returns 1: 'Kevin <b>van</b> <i>Zonneveld</i>'
// example 2: strip_tags('<p>Kevin <img src="someimage.png" onmouseover="someFunction()">van <i>Zonneveld</i></p>', '<p>');
// returns 2: '<p>Kevin van Zonneveld</p>'
// example 3: strip_tags("<a href='http://kevin.vanzonneveld.net'>Kevin van Zonneveld</a>", "<a>");
// returns 3: '<a href='http://kevin.vanzonneveld.net'>Kevin van Zonneveld</a>'
// example 4: strip_tags('1 < 5 5 > 1');
// returns 4: '1 < 5 5 > 1'
function strip_tags (str, allowed_tags)
{
var key = '', allowed = false;
var matches = []; var allowed_array = [];
var allowed_tag = '';
var i = 0;
var k = '';
var html = '';
var replacer = function (search, replace, str) {
return str.split(search).join(replace);
};
// Build allowes tags associative array
if (allowed_tags) {
allowed_array = allowed_tags.match(/([a-zA-Z0-9]+)/gi);
}
str += '';
// Match tags
matches = str.match(/(<\/?[\S][^>]*>)/gi);
// Go through all HTML tags
for (key in matches) {
if (isNaN(key)) {
// IE7 Hack
continue;
}
// Save HTML tag
html = matches[key].toString();
// Is tag not in allowed list? Remove from str!
allowed = false;
// Go through all allowed tags
for (k in allowed_array) { // Init
allowed_tag = allowed_array[k];
i = -1;
if (i != 0) { i = html.toLowerCase().indexOf('<'+allowed_tag+'>');}
if (i != 0) { i = html.toLowerCase().indexOf('<'+allowed_tag+' ');}
if (i != 0) { i = html.toLowerCase().indexOf('</'+allowed_tag) ;}
// Determine
if (i == 0) { allowed = true;
break;
}
}
if (!allowed) {
str = replacer(html, "", str); // Custom replace. No regexing
}
}
return str;
}
Usage if content is the editor content. This will keep Spans:
var my_clean_content = strip_tags( content, 'span');
Personally, I'd do this with jQuery and use a plugin such as Text Constrain.
You can download it here, or view other similar plugins on the jQuery site.
The best jQuery plugin I've seen that implements this functionality is Expander. It's also got the advantage of having been around for several years and is still actively maintained as of the time this was written unlike most or all of the other solutions linked to in answers to this question.
Related
One web page includes 3 different language words such as:
Language / 한국어 / ภาษาไทย
I'd like to enlarge Thai words(ภาษาไทย) in order to highlight it.
<span class="thai">ภาษาไทย</span>
Of course, it's possible if I specifiy 'class' property to all Thai sections, but it's too many to put them all.
If possible, I would like to change font-family & font-size according to unicode range with jquery.
Thank you.
You can use string.replace() to automatically tag every Thai word.
document.body.innerHTML = document.body.innerHTML.replace(
/([\u0E00-\u0E7F]+)/g,
function (x){return '<span class="thai">'+x+'</span>'}
);
See JSFiddle.
Although no JQuery, I would do something like this:
(In the case of Thai characters, they are in the Unicode range: 0E00-0E7F)
function StyleThaiInTag(tagName) {
var allTags = document.getElementsByTagName(tagName);
for (var i = 0; i < allTags.length; i++) {
var element = allTags[i];
var code = element.innerHTML.charCodeAt(0)
if (0x0E00 <= code && code <= 0x0E7F) {
// style element the way you want
element.style.fontSize = "2em";
//...
}
}
}
// example usage:
StyleThaiInTag('span');
StyleThaiInTag('div');
Here's a JSFiddle.
I have a jQuery Tree Report that I am trying to create 'expand/collapse all' buttons for.
The following two pieces of code are fired when the corresponding buttons are pressed and work great:
for (i = 1; i < 100; i++) {
var el = $('#dtt_2597807651112537_table tbody tr')[i - 1];
// store current level
var level = Number($(el).attr('dtt_level'));
// change icon
$(el).find('span.dtt_icon').removeClass('dtt_collapsed_span');
$(el).find('span.dtt_icon').addClass('dtt_expanded_span');
while ($($(el).next()).attr('dtt_level') != null) {
var el = $(el).next();
if ($(el).attr('dtt_level') == (level + 1)) {
// change display
el.removeClass('dtt_collapsed_tr');
el.addClass('dtt_expanded_tr');
} else if ($(el).attr('dtt_level') == level) {
break;
}
}
}
for (i = 1; i < 100; i++) {
// get related table row
var el = $('#dtt_2597807651112537_table tbody tr')[i - 1];
// store current level
var level = Number($(el).attr('dtt_level'));
// change icon
$(el).find('span.dtt_icon').addClass('dtt_collapsed_span');
$(el).find('span.dtt_icon').removeClass('dtt_expanded_span');
while ($($(el).next()).attr('dtt_level') != null) {
var el = $(el).next();
if ($(el).attr('dtt_level') > level) {
// change display
el.addClass('dtt_collapsed_tr');
el.removeClass('dtt_expanded_tr');
// change icon
$(el).find('span.dtt_icon').addClass('dtt_collapsed_span');
$(el).find('span.dtt_icon').removeClass('dtt_expanded_span');
} else if ($(el).attr('dtt_level') == level) {
break;
}
}
};
However, I was wondering if anyone had a nice way to:
1) Get the number of rows that need to be looped through - I just put 100 as a large number to prove my code worked and I don't want to just increase this to an even larger number.
2) Get the class name from the page source - The large number in "dtt_2597807651112537_table" is a report ID generated by the application. This is static for now but I want to eliminate any problems if it changes.
Thanks.
This is all wrong. Well, it's working against how jQuery works, in any case.
jQuery's credo is:
Select elements
Do stuff to them
Drop your loops. You don't need them.
For example. To toggle the icon on all span.dtt_icon in your document, do
var collapsed = true;
$("#dtt_2597807651112537_table span.dtt_icon") // select elements
.toggleClass('dtt_collapsed_span', collapsed) // do stuff to them
.toggleClass('dtt_expanded_span', !collapsed);
or, as a function that can both collapse and expand:
function toggleTree(tree, collapsed) {
$(tree).find("span.dtt_icon")
.toggleClass('dtt_collapsed_span', collapsed)
.toggleClass('dtt_expanded_span', !collapsed);
}
To collapse only the currently expanded ones...
$("#dtt_2597807651112537_table span.dtt_icon.dtt_expanded_span")
.toggleClass('dtt_collapsed_span', true)
.toggleClass('dtt_expanded_span', false);
and so on.
You can boil down your entire code into a few lines that way, and you don't need to write a single loop: Use smart element selection (via jQuery selectors and any of jQuerys find, filter and traversal functions) to single out the elements you want to manipulate and then manipulate them all at once in a single step.
To your second question. There are many ways, pick one:
use known page structure to determine the right table (e.g. $("div.main > table:first") or something to that effect)
use known table contents to determine the right table (e.g. $("table:has(span.dtt_icon)"))
use the table's other classes ($("table.treeReport") maybe?) or for example the table's ID with and a "starts-with" selector ($("table[id^=dtt_]")).
Again it's all about selecting your elements smartly. A dive into the jQuery API documentation, in this case the part about selectors, is recommended.
I’m trying to wrap multiple instances of a string found in html around a tag (span or abbr) using pure JS. I have found a way to do it by using the code:
function wrapString() {
document.body.innerHTML = document.body.innerHTML.replace(/string/g, ‘<tag>string</tag>');
};
but using this code messes with a link’s href or an input’s value so I want to exclude certain tags (A, INPUT, TEXTAREA etc.).
I have tried this:
function wrapString() {
var allElements = document.getElementsByTagName('*');
for (var i=0;i<allElements.length;i++){
if (allElements[i].tagName != "SCRIPT" && allElements[i].tagName != "A" && allElements[i].tagName != "INPUT" && allElements[i].tagName != "TEXTAREA") {
allElements[i].innerHTML = allElements[i].innerHTML.replace(/string/g, ‘<span>string</span>');
}
}
}
but it didn’t work as it gets ALL the elements containing my string (HTML, BODY, parent DIV etc.), plus it kept crushing my browser. I even tried with JQuery's ":containing" Selector but I face the same problem as I do not know what the string's container is beforehand to add it to the selector.
I want to use pure JavaScript to do that as I was planning on using it as a bookmark for quick access to any site but I welcome all answers regarding JQuery and other frameworks as well.
P.S. If something like that has already been answered I couldn't find it...
This is a quite complicated problem actually (you can read this detailed blog post about it).
You need to:
recurse on the dom tree
find all text nodes
do your replace on its data
make the modified data into dom nodes
insert the dom nodes to the tree, before the original text node
remove the original text node
Here is a demo fiddle.
And if you still need tagName based exclusions, look at this fiddle
The code:
function wrapInElement(element, replaceFrom, replaceTo) {
var index, textData, wrapData, tempDiv;
// recursion for the child nodes
if (element.childNodes.length > 0) {
for (index = 0; index < element.childNodes.length; index++) {
wrapInElement(element.childNodes[index], replaceFrom, replaceTo);
}
}
// non empty text node?
if (element.nodeType == Node.TEXT_NODE && /\S/.test(element.data)) {
// replace
textData = element.data;
wrapData = textData.replace(replaceFrom, replaceTo);
if (wrapData !== textData) {
// create a div
tempDiv = document.createElement('div');
tempDiv.innerHTML = wrapData;
// insert
while (tempDiv.firstChild) {
element.parentNode.insertBefore(tempDiv.firstChild, element);
}
// remove text node
element.parentNode.removeChild(element);
}
}
}
function wrapthis() {
var body = document.getElementsByTagName('body')[0];
wrapInElement(body, "this", "<span class='wrap'>this</span>");
}
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.
I was wondering if it is possible to use JavaScript to add a <div> tag around a word in an HTML page.
I have a JS search that searches a set of HTML files and returns a list of files that contain the keyword. I'd like to be able to dynamically add a <div class="highlight"> around the keyword so it stands out.
If an alternate search is performed, the original <div>'s will need to be removed and new ones added. Does anyone know if this is even possible?
Any tips or suggestions would be really appreciated.
Cheers,
Laurie.
In general you will need to parse the html code in order to ensure that you are only highlighting keywords and not invisible text or code (such as alt text attributes for images or actual markup). If you do as Jesse Hallett suggested:
$('body').html($('body').html().replace(/(pretzel)/gi, '<b>$1</b>'));
You will run into problems with certain keywords and documents. For example:
<html>
<head><title>A history of tables and tableware</title></head>
<body>
<p>The table has a fantastic history. Consider the following:</p>
<table><tr><td>Year</td><td>Number of tables made</td></tr>
<tr><td>1999</td><td>12</td></tr>
<tr><td>2009</td><td>14</td></tr>
</table>
<img src="/images/a_grand_table.jpg" alt="A grand table from designer John Tableius">
</body>
</html>
This relatively simple document might be found by searching for the word "table", but if you just replace text with wrapped text you could end up with this:
<<span class="highlight">table</span>><tr><td>Year</td><td>Number of <span class="highlight">table</span>s made</td></tr>
and this:
<img src="/images/a_grand_<span class="highlight">table</span>.jpg" alt="A grand <span class="highlight">table</span> from designer John <span class="highlight">Table</span>ius">
This means you need parsed HTML. And parsing HTML is tricky. But if you can assume a certain quality control over the html documents (i.e. no open-angle-brackets without closing angle brackets, etc) then you should be able to scan the text looking for non-tag, non-attribute data that can be further-marked-up.
Here is some Javascript which can do that:
function highlight(word, text) {
var result = '';
//char currentChar;
var csc; // current search char
var wordPos = 0;
var textPos = 0;
var partialMatch = ''; // container for partial match
var inTag = false;
// iterate over the characters in the array
// if we find an HTML element, ignore the element and its attributes.
// otherwise try to match the characters to the characters in the word
// if we find a match append the highlight text, then the word, then the close-highlight
// otherwise, just append whatever we find.
for (textPos = 0; textPos < text.length; textPos++) {
csc = text.charAt(textPos);
if (csc == '<') {
inTag = true;
result += partialMatch;
partialMatch = '';
wordPos = 0;
}
if (inTag) {
result += csc ;
} else {
var currentChar = word.charAt(wordPos);
if (csc == currentChar && textPos + (word.length - wordPos) <= text.length) {
// we are matching the current word
partialMatch += csc;
wordPos++;
if (wordPos == word.length) {
// we've matched the whole word
result += '<span class="highlight">';
result += partialMatch;
result += '</span>';
wordPos = 0;
partialMatch = '';
}
} else if (wordPos > 0) {
// we thought we had a match, but we don't, so append the partial match and move on
result += partialMatch;
result += csc;
partialMatch = '';
wordPos = 0;
} else {
result += csc;
}
}
if (inTag && csc == '>') {
inTag = false;
}
}
return result;
}
Wrapping is pretty easy with jQuery:
$('span').wrap('<div class="highlight"></div>'); // wraps spans in a b tag
Then, to remove, something like this:
$('div.highlight').each(function(){ $(this).after( $(this).text() ); }).remove();
Sounds like you will have to do some string splitting, though, so wrap may not work unless you want to pre-wrap all your words with some tag (ie. span).
The DOM API does not provide a super easy way to do this. As far as I know the best solution is to read text into JavaScript, use replace to make the changes that you want, and write the entire content back. You can do this either one HTML node at a time, or modify the whole <body> at once.
Here is how that might work in jQuery:
$('body').html($('body').html().replace(/(pretzel)/gi, '<b>$1</b>'));
couldn't you just write a selector as such to wrap it all?
$("* :contains('foo')").wrap("<div class='bar'></div>");
adam wrote the code above to do the removal:
$('div.bar').each(function(){ $(this).after( $(this).text() ); }).remove();
edit: on second thought, the first statement returns an element which would wrap the element with the div tag and not the sole word. maybe a regex replace would be a better solution here.