Replace multiple <br>'s with only one <br> - javascript

How do I use JavaScript to detect
<br>
<br>
<br>
to become one
<br>
?
I tried with:
jQuery('body').html().replace(/(\<br\>\r\n){3, }/g,"\n");
but this is not working for me.

CSS Solution
If you want to disable the effect of multiple <br> on the page, you can do it by CSS without using JavaScript:
br + br { display: none; }
Check the jsFiddle demo.
However, this method is ideal when you are working with tags, something like this:
<div>Text</div><br /><br /><br />
<div>Text</div><br /><br /><br />
<div>Text</div><br /><br /><br />
In other cases, like this:
Hello World<br /> <br />
Hello World<br /> <br />
Hello World<br /> <br />
It will fail (as CSS passes text nodes). Instead, use a JavaScript solution.
JavaScript Solution
// It's better to wait for document ready instead of window.onload().
window.onload = function () {
// Get all `br` tags, defined needed variables
var br = document.getElementsByTagName('br'),
l = br.length,
i = 0,
nextelem, elemname, include;
// Loop through tags
for (i; i < l - 1; i++) {
// This flag indentify we should hide the next element or not
include = false;
// Getting next element
nextelem = br[i].nextSibling;
// Getting element name
elemname = nextelem.nodeName.toLowerCase();
// If element name is `br`, set the flag as true.
if (elemname == 'br') {
include = true;
}
// If element name is `#text`, we face text node
else if (elemname == '#text') {
// If text node is only white space, we must pass it.
// This is because of something like this: `<br /> <br />`
if (! nextelem.data.replace(/\s+/g, '').length) {
nextelem = br[i+1];
include = true;
}
}
// If the element is flagged as true, hide it
if (include) {
nextelem.style.display = 'none';
}
}
};
Check the jsFiddle demo.

What is the point of sending HTML, which is in a form that you don't want, to the client browser and making it run JavaScript code to clean it up? This looks like a bad design.
How about fixing all your static HTML, and HTML generation, so that these superfluous <br> elements do not occur in the first place?
If you use JavaScript to modify the document object, do so for dynamic effects that cannot be achieved in any other way.

Simpler:
var newText = oldText.replace(/(<br\s*\/?>){3,}/gi, '<br>');
This will allow optional tag terminator (/>) and also spaces before tag end (e.g. <br /> or <br >).

Wouldn't something like this be the right approach:
$("br~br").remove()
EDIT: No, it's wrong, because its definition of "contiguous" is too loose, as per BoltClock.

This solution is jQuery + DOM only, does not manipulate HTML as string, works with text nodes, ignores whitespace only text nodes:
$('br').each(function () {
const {nodeName} = this;
let node = this;
while (node = node.previousSibling) {
if (node.nodeType !== Node.TEXT_NODE || node.nodeValue.trim() !== '') {
break;
};
}
if (node && node !== this && node.nodeName === nodeName) {
$(node).remove();
}
});
See: https://jsfiddle.net/kov35jct/

Try this
$('body').html($('body').html().replace(/(<br>)+/g,"<br>"));
It will replace n number of <br> into one.
Demo

I would go with this:
$('body').html($('body').html().replace(/<br\W?\\?>(\W?(<br\W?\\?>)+)+/g,"<br>"));
However, after reading the comments in another post here I do consider that you should try to avoid doing this in case you can correct it in the back end.

A lot of the other answers to this question will only replace up to certain amount of elements, or use complex loops. I came up with a simple regex that can be used to replace any number of <br> tags with a single tag. This works with multiple instances of multiple tags in a string.
/(<br>*)+/g
To implement this in JavaScript, you can use the String.replace method:
myString.replace(/(<br>*)+/g, "<br/>");
To replace multiple <br/> tags, add a / to the regex:
/(<br\/>*)+/g

Try this:
jQuery('body').html(
jQuery('body').html().replace(/(?:<br>\s+){3,}/ig,"\n"));
);
DEMO:
jsfiddle

Related

jquery if input contains phrase from array-child friendly global chat

I have found some code for a chat system. I am going to be using it as a child-friendly (so I can't be blamed for anything) global chat. The way the code works is by checking the input to see if it contains any word from an array, if it does then the program will display something to a <ol> tag, for me to see if it works. Otherwise is does nothing.
JQUERY
var banned_words = {
'songs': ['hello', 'sorry', 'blame'],
'music': ['tempo', 'blues', 'rhythm']
};
function contains(words) {
return function(word) {
return (words.indexOf(word) > -1);
};
};
function getTags(input, banned_words) {
var words = input.match(/\w+/g);
return Object.keys(banned_words).reduce(function(tags, classification) {
var keywords = banned_words[classification];
if (words.some(contains(keywords)))
tags.push(classification);
return tags;
}, []);
};
// watch textarea for release of key press
$('#sendie').keyup(function(e) {
$('#tags').empty();
var tags = getTags($(this).val().toLowerCase(), banned_words);
var children = tags.forEach(function(tag) {
$('#tags').append($('<li>').text(tag));
});
if (e.keyCode == 13) {
var text = $(this).val();
var maxLength = $(this).attr("maxlength");
var length = text.length;
// send
if (length <= maxLength + 1) {
chat.send(text, name);
$(this).val("");
} else {
$(this).val(text.substring(0, maxLength));
}
}
});
HTML
<form id="send-message-area">
<p style="text-align:center">Your message: </p>
<input id="sendie" maxlength = '100' />
</form>
<ol id="tags">
</ol>
But, what I'm also wanting to do is check if the input value contains phrases from the array so I can ban phrases that are too sensitive for children. How can I add this in, or is there another more efficient way to check the input?
UPDATE
I have already tried to place the phrase directly into the banned_words (I changed them as this post would get flagged for inappropriate language) and using the hexadecimal character for the space-bar. None of these worked.
You can try either of the following since the space is also a known charachter:
test 1:
Just put your phrase between quotes such as 'cry baby','red apple' etc.
sometimes it does not work, you can try
test 2:
replace the space with the hexidecimal character \x20 such as 'cry\x20baby','red\x20apple'
I hope one or both of these works for you.
I have done some more research and have found a cool plugin for JQuery. It's called jQuery.profanityFilter. It uses a json file filled with sensative words.
Link to github download: https://github.com/ChaseFlorell/jQuery.ProfanityFilter

Allow only roman characters in text fields

I am finding a way to make all the text boxes in the website only accept roman characters. Is there any easy way to do it globally.
Thanks in advance.
In modern browsers <input> accepts an attribute called pattern. This allows to restrict the valid characters in a given field.
input:invalid {
background-color:red;
}
<form>
<input type="text" pattern="[a-zA-Z\s\.\-_]+" />
<button type="submit">Submit</button>
</form>
For all other browsers you can find all form field via jQuery, check if a pattern-attribute exists, and check it against the value of a given field. You may also replace disallowed characters:
$('form').on('keyup blur','input',function() {
if ($(this).val() && $(this).attr('pattern')) {
//var pattern = new RegExp('^'+$(this).attr('pattern')+'$', 'g');
//$(this).toggleClass('invalid', pattern.match(!$(this).val()));
var pattern = new RegExp($(this).attr('pattern').replace(/\[/,'[^'), 'g');
$(this).val($(this).val().replace(pattern,''));
}
});
input:invalid {
background-color:red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.0/jquery.min.js"></script>
<form>
<input type="text" pattern="[a-zA-Z\s\.\-_]+" />
<button type="submit">Submit</button>
</form>
Oh, you still want to validate form inputs on the server-side. All HTML- or Javascript-stuff does not prevent all visitors of your site to submit broken stuff.
I will refer to the marked answer for the following question for the regex which filters out non-roman characters:
How to detect non-roman characters in JS?
Spoiler: the regex is /[^\u0000-\u024F\u1E00-\u1EFF\u2C60-\u2C7F\uA720-\uA7FF]/g
Now all you need is a little bit of tinkering with jQuery:
var myInputId = "#foo"; // Or whatever you wish to use.
var input = $(myInputId);
var exp = /[^\u0000-\u024F\u1E00-\u1EFF\u2C60-\u2C7F\uA720-\uA7FF]/g;
input.blur(function() {
input.value = input.value.replace(exp, "");
});
Include this snippet into your master page for example:
<script>
$(function(){
$('input[type=text],textarea').keypress(function(e){
var char = String.fromCharCode(e.which || e.charCode);
var rgx = /[\u0000-\u007F]/;
if (rgx.test(char) == false)
return false;
})
})
</script>
Here is my idea based on #fboes answer.
I also needed to show user whats wrong, so there is error message showing but with no redundancy when typing couple of forbidden characters in a row.
//I wanted first to assign pattern attr to every input in form but when it's happening, all "\" chars are removed from regex therefore - it doesn't work, so I had to add it in templates for every input.
let isIncorrect = false;
scope.checkPattern = function(e) {
// I don't want to allow Chineese, cyrylic chars but some other special - yes
var pattern = new RegExp('[a-zA-Z\s\.\-_äÄöÖüÜßąćęłńóśźżĄĆĘŁŃÓŚŹŻ]+', "g");
if ($(e).is(':valid')){
return true
} else {
$(e).val($(e).val().replace(pattern,''));
return false
}
};
scope.removeAlert = function (e){
$(e).parent().find('.text-danger').remove();
isIncorrect = false;
}
// unallowed characters in order inputs
$('.my-form').on('keyup blur','input',function(e) {
if (!scope.checkPattern($(this))) {
if (!isIncorrect){
// show this error message but only once (!) and for specified period of time
$(this).parent().append('<p class="text-danger">Only latin characters allowed</p>');
isIncorrect = true;
}
setTimeout(scope.removeAlert, 3000, $(this));
}
});

Removing leading whitespace from indented HTML source in pre/code tags

I currently have the following html within a pre-code block:
<pre class="prettyprint"><code>
<html>
<body>
<form name="input" action="html_form_action.asp" method="get">
<input type="radio" name="sex" value="male">Male<br>
<input type="radio" name="sex" value="female">Female<br>
<input type="submit" value="Submit">
</form>
<p>If you click the "Submit" button, the form-data will be sent to a page called "html_form_action.asp".</p>
</body>
</html>
</code></pre>
It is indented within the html source for better structure within the document. How can I remove the leading whitespace? Through the use of javascript or is there a more simple method.
The question asks if there's a JavaScript solution or a simpler method for removing leading whitespace. There's a simpler method:
CSS
pre, code {
white-space: pre-line;
}
DEMO
white-space
The white-space property is used to describe how whitespace inside the
element is handled.
pre-line
Sequences of whitespace are collapsed.
To preserve existing whitespace or indentation, each line can be wrapped in a child tag with white-space reset to pre at the line level:
HTML
<pre>
<code>
<i>fn main() {</i>
<i> println!("hello world!");</i>
<i>}</i>
</code>
</pre>
CSS
pre, code {
white-space: pre-line;
}
pre > code > i {
white-space: pre;
}
You may want to just change how it is output, but it is fairly simple to do with JavaScript
var p = document.querySelector(".prettyprint");
p.textContent = p.textContent.replace(/^\s+/mg, "");
http://jsfiddle.net/a4gfZ/
I really like Homam's idea, but I had to change it to deal with this:
<pre><code><!-- There's nothing on this line, so the script thinks the indentation is zero -->
foo = bar
</code></pre>
To fix it, I just take out the first line if it's empty:
[].forEach.call(document.querySelectorAll('code'), function($code) {
var lines = $code.textContent.split('\n');
if (lines[0] === '')
{
lines.shift()
}
var matches;
var indentation = (matches = /^[\s\t]+/.exec(lines[0])) !== null ? matches[0] : null;
if (!!indentation) {
lines = lines.map(function(line) {
line = line.replace(indentation, '')
return line.replace(/\t/g, ' ')
});
$code.textContent = lines.join('\n').trim();
}
});
(I'm also processing <code> tags instead of <pre> tags.)
Extending the above solution, this snippet assumes the indentation of the the first line inside <pre> is 0 and it realigns all the lines based on the first line:
[].forEach.call(document.querySelectorAll('pre'), function($pre) {
var lines = $pre.textContent.split('\n');
var matches;
var indentation = (matches = /^\s+/.exec(lines[0])) != null ? matches[0] : null;
if (!!indentation) {
lines = lines.map(function(line) {
return line.replace(indentation, '');
});
return $pre.textContent = lines.join('\n').trim();
}
});
When you use pre, you should format its content to be exactly as you want it to be rendered. That’s the very idea of pre (preformatted text). But if it’s just indentation, you could use CSS: margin-left with a suitable negative value.

How to filter # followed by an "a" element...and the following <br> elements?

I've changed my code, so now lets say there is this:
<p>
#Mr. Jingles
<br>
<br>
This would be where the person would say something. // I want to start here
<br>
<br>
This is even more text.
</p>
<p>
#Lady Ladington
<br>
<br>
Hello!
</p>
What I want to do is delete the first <a> element, then delete all of the <br> elements until where the person would say something.
When successful, it should look like:
<p>
This would be where the person would say something.
<br>
<br>
This is even more text.
</p>
<p>
Hello!
</p>
Note: These would be people commenting on a blog post.
I can only think of using Regexp in the innerHtml property of the <p></p>.
function remove (elem, name) {
var content = elem.innerHTML,
regex = new RegExp('#<a.*?>' + name + '<\/a>(\n)*(<br\s?\/?>|\n)*', 'mgi');
elem.innerHTML = content.replace(regex, '');
}
remove(paragr, 'Wiki Tiki');
And you should probably work on the regular expression, to handle all your scenarios, you could get newline characters \n and/or xhtml breaks <br />
Problem is that it's going to be a bit slow because of the innerHTML manipulation, but try it out, maybe it'll be fast enough.
Assuming it's wrapped in an element with the ID of "theContent":
var content = document.getElementById('theContent');
var c = content.innerHTML;
c = c.replace(/#<a([^\/])+\/a>/,'');
c = c.replace(/(\<br>(\s)+)+/,'');
content.innerHTML = c;
This probably has to take into account some more checks, but it could be something like this:
$('p')
.children().filter(function() {
return this.nodeType == Node.TEXT_NODE && $(this).text() == '#';
})
.next('a')
.nextUntil(':not(br)')
.andSelf()
.andSelf()
.remove();
Also, this will not work in IE7 and earlier. For that use this.nodeType == 3.
You could do something like this, I'm not sure how efficient it would be, and you may have to alter the logic depending on the text contained in your page -
$("p").contents().each(function() {
if (this.nodeType == 3 && this.nodeValue.indexOf("This would be") > -1) return false;
$(this).remove()
})
The code just removes all nodes until it finds the text node with the words "This would be" in it.
Demo - http://jsfiddle.net/ipr101/TTuDw/3/

Targeting plain text with jQuery

I'm trying to isolate (and then manipulate) quote block formatted in common newsreader and email-client manner.
The HTML:
<p>
Hello there!
<br />
I'm great, how are you?
<br />
<br />
Someone wrote:
<br />
> Greetings,
<br />
> How are you?
</p>
I need to target all the lines that start with >, and hide them as a collapsable block. In the above example everything bellow "Someone wrote:" would then be hidden, saved as a variable and the end result produced by JS would be:
<p>
Hello there!
<br />
I'm great, how are you?
<br />
<br />
Someone wrote:
<br />
Click to expand
</p>
Gmail does the same thing, but it serverside wraps the quotation block in a <div>, due to the specific nature of my project the complete process has to be done by JS alone.
I'm working with the jQuery framework.
Thanks in advance!
I don't believe jquery parses text like this. You'll have to parse it yourself for lines that start with '>' and edit the string as you'd like. Then you can use jquery to act on the elements you added.
I put together an example for you at this pastebin. Here is the code with comments added.
HTML
<p>
Hello there!
<br />
I'm great, how are you?
<br />
<br />
Someone wrote:
<br />
> Greetings,
<br />
> How are you?
<br />
<br />
Someone else wrote:
<br />
> I like turtles
<br />
<br />
> Someone odd person wrote:
<br />
> > You smell like cheese
<br />
> > and now I'm hungry
<br />
<br />
and that's the end,
<br />
of all of this.
</p>
Script
$(document).ready(function(){
// link text to inform users to click to expand
var lnk = '[+]';
// variable to look for stating it's a new reply
var newrply = 'wrote:';
// reply indicator (HTML escape code for ' > ' to exclude any HTML that might be found
var isrply = '> ';
// collect html and split it into an array
var txt = $('p').html().split('<br>');
// flag showing that the text is within a reply block
var rply = false;
// cycle through each portion of text
$.each(txt, function(i){
// look for a new reply
if (this.match(newrply)){
// if within a reply and it finds a new reply, close previous
var tmp = (rply) ? '</div>' : '';
// add link
txt[i] = tmp + txt[i] + ' ' + lnk + '';
// go to next variable in array and add wrapper, this makes sure the <br> is outside the reply (formatting purposes)
txt[i+1] = '<div class="reply">' + txt[i+1];
// look for reply indicator or text that is <5 characters in length
// (in the HTML above, the array value will have carriage return plus two spaces for each <br>)
} else if (this.match(isrply) || txt[i].length < 5) {
rply = true;
} else {
rply = false;
// close the reply, add the close to the previous array element (most likely a <br>)
txt[i-1] = txt[i-1] + '</div>';
}
// close the reply at the end of the array
if(i == txt.length) {
txt[i-1] = txt[i-1] + '</div>';
}
})
// join the array and add it back
$('p').html( txt.join('<br>') );
// hide the replies
$('.reply').hide();
// add toggle view
$('.replylink').click(function(){
$(this).next().next('.reply').toggle();
return false;
})
})
I changed the link to just a '[+]' to toggle the view but I didn't bother changing it to '[-]' when the reply is open. I figured the code was getting long enough as it is for this example.
With the new code you posted, I had to make a few changes.
It will now work with multiple posts (it processes each "div.post")
It will now only find a new reply if the ">" is at the beginning of a new line
It uses the rel tag to index each reply since the .next() function of jQuery will find "" and the number of these was variable
One problem I had was with the click function, I ended up switching to .live because the click event was being triggered twice (I couldn't figure out why, but using live works).
Lastly, I left the <a name="" style="color: gray;"/> in the code, but that is not properly formatted HTML... you can't close an <a> tag this way.
New Update:
Fixed the script to work with IE, apparently IE uses <BR> instead of <br> so the split wasn't working. I ended up using $.browser.msie even though it isn't recommended. Also, the original script left unopened </div> which is why it broke in IE as well.
The rply variable I used before wasn't updating between the iterations of the each function, so I moved it's value into a hidden input tag. I tried making it global, but it just wouldn't cooperate. It's probably not the ideal way to do this, so fix/adjust as you desire.
Required HTML
<input id="replyflag" type="hidden" value="false"/>
Updated Code for IE & new pastbin posting:
$(document).ready(function(){
$('div.post').each(function(){
// link text to inform users to click to expand
var lnk = '[+]';
// variable to look for stating it's a new reply
var newrply = 'wrote:';
// reply indicator (HTML escape code for ' > ' to exclude any HTML that might be found
var isrply = '>';
// IE capitalizes the <BR>, collect html and split it into an array
var splt = ($.browser.msie) ? '<BR>' : '<br>';
var txt = $(this).find('p:eq(0)').html().split(splt);
// index of each reply in a post
var indx = 0;
// start <div> tag around contents, as the script automatically closes the tag, even without replies
txt[0] = '<div>' + txt[0];
// cycle through each portion of text
$.each(txt, function(i){
// look for a new reply
if (this.match(newrply)){
// if within a reply and it finds a new reply, close previous
var tmp = ($('#replyflag').val()) ? '</div>' : '';
// set the "within a reply flag" to true
$('#replyflag').val(true);
// increment index
indx++;
// add link, the rel attrib contains the index of the reply
txt[i] = tmp + txt[i] + ' ' + lnk + '';
// go to next variable in array and add wrapper, this makes sure the <br> is outside the reply (formatting purposes)
txt[i+1] = '<div class="reply" rel="' + indx + '">' + txt[i+1];
// look for reply indicator at the beginning of a line or text that is > 3 characters in length, if not there, turn off reply flag.
} else if (this.substring(0,4)!=isrply | this.length > 3) {
$('#replyflag').val(false);
}
// close the reply at the end of the array
if (i >= txt.length-1) {
txt[i] = txt[i] + '</div>';
}
})
// join the array and add it back
$(this).find('p:eq(0)').html( txt.join('<br>') );
// hide the replies
$('.reply').hide();
// add toggle view (using live because sometimes the click event is called twice and the toggle appears to not work)
$('.replylink').live('click',function(){
$(this).parent().find('.reply[rel=' + $(this).attr('rel') + ']').toggle();
return false;
})
})
})

Categories

Resources