I'm using a search-function for a documentation site which upon selection of search hit shows page with text highlighted (just as a pdf-reader or netbeans would do).
To achive the highlight i use javascript with:
function searchHighlight(searchTxt) {
var target = $('#page').html();
var re = new RegExp(searchTxt, 'gi');
target = target.replace(
re,
'<span class="high">' + searchTxt + '</span>'
);
$('#page').html(target);
}
Problem / Question:
Since page incudes images with filenames based on md5, some searches messes up the image src.
Searching on "1000" will distort the
<img src="53451000abababababa---.jpg"
to
<img src="5334<span class="hl">1000</span>abababab--.jpg">
Is it possible to solve this with regexp, somehow excluding anything anjcent to ".jpg"?
Or would it be possible to, before highligting replace the images with placeholders, and after replace revert back to src?
Example:
replace all <img *> with {{I-01}}, {{I-02}} etc and keep the real src in a var.
Do the replace above.
Revert back from {{I-01}} to the <img src=".."/>
DOM-manipulation is of course an option, but I figure this could be done with regexp somehow, however, my regexp skills are lacking badly.
UPDATE
This code works for me now:
function searchHighlight(searchTxt) {
var stack = new Array();
var stackPtr = 0;
var target = $('#page').html();
//pre
target = target.replace(/<img.+?>/gi,function(match) {
stack[stackPtr] = match;
return '{{im' + (stackPtr++) + '}}';
});
//replace
var re = new RegExp(searchTxt, 'gi');
target = target.replace(re,'<span class="high">' + searchTxt + '</span>');
//post
stackPtr = 0;
target = target.replace(/{{im.+?}}/gi,function(match) {
return stack[stackPtr++];
});
$('#page').html(target);
}
One approach would be to create an array of all possible valid search terms. Set the terms as .textContent of <span> elements within #page parent element.
At searchHighlight function check if searchTxt matches an element within array. If searchTxt matches an element of array, select span element using index of matched array element, toggle "high" .className at matched #page span element, else notify user that searchTxt does not match any valid search terms.
$(function() {
var words = [];
var input = $("input[type=text]");
var button = $("input[type=button][value=Search]");
var reset = $("input[type=button][value=Reset]");
var label = $("label");
var page = $("#page");
var contents = $("h1, p", page).contents()
.filter(function() {
return this.nodeType === 3 && /\w+/.test(this.nodeValue)
}).map(function(i, text) {
var span = text.nodeValue.split(/\s/).filter(Boolean)
.map(function(word, index) {
words.push(word);
return "<span>" + word + "</span> "
});
$(text.parentElement).find(text).replaceWith(span);
})
var spans = $("span", page);
button.on("click", function(event) {
spans.removeClass("high");
label.html("");
if (input.val().length && /\w+/.test(input.val())) {
var terms = input.val().match(/\w+/g);
var indexes = $.map(terms, function(term) {
var search = $.map(words, function(word, index) {
return word.toLowerCase().indexOf(term.toLowerCase()) > -1 && index
}).filter(Boolean);
return search
});
if (indexes.length) {
$.each(indexes, function(_, index) {
spans.eq(index).addClass("high")
})
} else {
label.html("Search term <em>" + input.val() + "</em> not found.");
}
}
});
reset.on("click", function(event) {
spans.removeClass("high");
input.val("");
label.html("");
})
})
.high {
background-color: #caf;
}
label em {
font-weight: bold;
background-color: darkorange;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="text" />
<input type="button" value="Search" />
<input type="button" value="Reset" />
<label></label>
<div id="page" style="max-width:500px;border:1px solid #ccc;">
<h1 style="margin:0px;">test of replace</h1>
<p>After Luke comes to Dagobah, Yoda initially withholds his true identity. He’s trying to get a sense of who Luke is as a person; Yoda understands that there’s a lot at risk in training Luke to be a Jedi, especially considering what happened with his
father.
<img style="float:right;" width="200" src="http://a.dilcdn.com/bl/wp-content/uploads/sites/6/2013/11/04-400x225.jpg">And Yoda is not impressed — Luke is impatient and selfish. With “Adventure. Excitement. A Jedi craves not these things,” the Jedi Master makes clear that Luke must understand the significance and meaning of the journey he thinks he wants to make.
It’s an important lesson for Luke and for audiences, because when Luke faces Vader at the film’s climax, we see the stakes involved in the life of a Jedi</p>
<p>Now Yoda-search works, however a search on "sites" will break the image-link. (Yes, I know this implementation isn't perfect but I'm dealing with reality)</p>
</div>
Related
I have a string of text here that will be dynamically generated to be one of the following:
<h1 id="headline">"Get your FREE toothbrush!"</h1>
OR
<h1 id="headline">"FREE floss set and dentures!"</h1>
Since this will be dynamically generated I won't be able to wrap a <span> around the word "FREE" so I want to specifically target the word "FREE" using Javascript so that I can style it with a different font-family and font-color than whatever styling the <h1> is set to. What methods do I use to go about doing this?
You can search and replace the substring 'FREE' with styled HTML. If 'FREE' occurs more than once in the string you may need to use regex (unless you don't need to support Internet Explorer). See How to replace all occurrences of a string?
In your case:
let str = '<h1 id="headline">"FREE floss set and dentures!"</h1>'
str = str.replace(/FREE/g, '<span color="red">FREE</span>');
The property you are looking for is innerHTML, look the following example:
var word = document.getElementById('word');
function changeWord(){
word.innerHTML = "another";
word.style.backgroundColor = 'black';
word.style.color = 'white';
}
<h1 id="headline">
<span id="word">some</span> base title
</h1>
<button onClick="changeWord()">
change
</button>
Here is a working example using slice and some classic concatenation.
EDIT: Code for the second string is also included now.
//get headline by id
var headline = document.getElementById("headline");
//declare your possible strings in vars
var string1 = "Get your FREE toothbrush!"
var string2 = "FREE floss set and dentures!"
//declare formatted span with "FREE" in var
var formattedFree = "<span style='color: blue; font-style: italic;'>FREE</span>"
//target positions for the rest of your string
var string1Position = 13
var string2Position = 4
//concat your vars into expected positions for each string
var newString1 = string1.slice(0, 9) + formattedFree + string1.slice(string1Position);
var newString2 = formattedFree + string2.slice(string2Position)
//check if strings exist in html, if they do then append the new strings with formatted span
if (headline.innerHTML.includes(string1)) {
headline.innerHTML = newString1
}
else if (headline.innerHTML.includes(string2)) {
headline.innerHTML = newString2
}
<!-- As you can see the original string does not have "FREE" formatted -->
<!-- Change this to your other string "FREE floss set and dentures!" to see it work there as well -->
<h1 id="headline">Get your FREE toothbrush!</h1>
You can split the text and convert the keyword "FREE" to a span element. So you can style the keyword "FREE". This method is safe because does not alter any non-text html element.
var keyword = "FREE";
var headline = document.getElementById("headline");
var highlight, index;
headline.childNodes.forEach(child => {
if (child.nodeType == Node.TEXT_NODE) {
while ((index = child.textContent.indexOf(keyword)) != -1) {
highlight = child.splitText(index);
child = highlight.splitText(keyword.length);
with(headline.insertBefore(document.createElement("span"), highlight)) {
appendChild(highlight);
className = 'highlight';
}
}
}
});
.highlight {
/* style your keyword */
background-color: yellow;
}
<div id="FREE">
<h1 id="headline">"Get your FREE toothbrush! FREE floss set and dentures!"</h1>
</div>
When search any text on search box, it can be find and highlighted the correct text, but when search next/new text, it's unable to find the next/new text, it's not working when search again, i'm unable to find the issue. The JS below.
JS
$('button#search').click(function() {
var page = $('#ray-all_text');
var pageText = page.html().replace("<span>", "").replace("</span>");
var searchedText = $('#searchfor').val();
var theRegEx = new RegExp("(" + searchedText + ")", "igm");
var newHtml = pageText.replace(theRegEx, "<span>$1</span>");
page.html(newHtml);
$('html, body').animate({
scrollTop: $("#ray-all_text span").offset().top }, 2000);
});
HTML
<div class="ray-search">
<div class="field" id="ray-search-form">
<input type="text" id="searchfor" placeholder="what are you searching for?" />
<button type="button" id="search">Press to Find!</button>
</div>
</div>
<article id="ray-all_text">
<p>
This manual and web site, all information and data and photos contained herein, are the s...
</p>
</article>
Please check the live Example: https://jsfiddle.net/gaezs6s8
Why is this happening? Is there a solution?
My suggestion is to make a few validations before change all the .html() inside the text you want to avoid unexpected behaviors and improve the functionality.
First make a validation to avoid the 'space' as the first value on the input, this will let us later check if the input has a real value inside.
$('body').on('keydown', '#searchfor', function(e) {
if (e.which === 32 && e.target.selectionStart === 0) {
return false;
}
});
Code from this answer
Now Please check the comments on your code:
//Create some vars to later check for:
//['text you are searching', 'number of highlights','actual highlight']
var searching,
limitsearch,
countsearch;
$('button#search').click(function() {
var searchedText = $('#searchfor').val();
var page = $('#ray-all_text');
//Now check if the text on input is valid
if (searchedText != "") {
//If the actual text on the input is different from the prev search
if(searching != searchedText) {
page.find('span').contents().unwrap();
var pageText = page.html();
var theRegEx = new RegExp("(" + searchedText + ")", "igm");
var newHtml = pageText.replace(theRegEx, "<span>$1</span>");
page.html(newHtml);
//Set your variables to the actual search
searching = searchedText;
limitsearch = page.find('span').length;
countsearch=0;
} else {
//If it's the same of the prev search then move to next item instead of repaint html
countsearch<limitsearch-1 ? countsearch++ : countsearch=0;
console.log(countsearch+'---'+limitsearch)
}
//Go to target search
$('body').animate({
scrollTop: $("#ray-all_text span").eq(countsearch).offset().top - 50},
200);
} else {
alert('empty search')
}
});
JqueryDemo
I have a list with about 10 000 customers on a web page and need to be able to search within this list for matching input. It works with some delay and I'm looking for the ways how to improve performance. Here is simplified example of HTML and JavaScript I use:
<input id="filter" type="text" />
<input id="search" type="button" value="Search" />
<div id="customers">
<div class='customer-wrapper'>
<div class='customer-info'>
...
</div>
</div>
...
</div>
<script type="text/javascript">
$(document).ready(function() {
$("#search").on("click", function() {
var filter = $("#filter").val().trim().toLowerCase();
FilterCustomers(filter);
});
});
function FilterCustomers(filter) {
if (filter == "") {
$(".customer-wrapper").show();
return;
}
$(".customer-info").each(function() {
if ($(this).html().toLowerCase().indexOf(filter) >= 0) {
$(this).parent().show();
} else {
$(this).parent().hide();
}
});
}
</script>
The problem is that when I click on Search button, there is a quite long delay until I get list with matched results. Are there some better ways to filter list?
1) DOM manipulation is usually slow, especially when you're appending new elements. Put all your html into a variable and append it, that results in one DOM operation and is much faster than do it for each element
function LoadCustomers() {
var count = 10000;
var customerHtml = "";
for (var i = 0; i < count; i++) {
var name = GetRandomName() + " " + GetRandomName();
customerHtml += "<div class='customer-info'>" + name + "</div>";
}
$("#customers").append(customerHtml);
}
2) jQuery.each() is slow, use for loop instead
function FilterCustomers(filter) {
var customers = $('.customer-info').get();
var length = customers.length;
var customer = null;
var i = 0;
var applyFilter = false;
if (filter.length > 0) {
applyFilter = true;
}
for (i; i < length; i++) {
customer = customers[i];
if (applyFilter && customer.innerHTML.toLowerCase().indexOf(filter) < 0) {
$(customer).addClass('hidden');
} else {
$(customer).removeClass('hidden');
}
}
}
Example: http://jsfiddle.net/29ubpjgk/
Thanks to all your answers and comments, I've come at least to solution with satisfied results of performance. I've cleaned up redundant wrappers and made grouped showing/hiding of elements in a list instead of doing separately for each element. Here is how filtering looks now:
function FilterCustomers(filter) {
if (filter == "") {
$(".customer-info").show();
} else {
$(".customer-info").hide();
$(".customer-info").removeClass("visible");
$(".customer-info").each(function() {
if ($(this).html().toLowerCase().indexOf(filter) >= 0) {
$(this).addClass("visible");
}
});
$(".customer-info.visible").show();
}
}
And an test example http://jsfiddle.net/vtds899r/
The problem is that you are iterating the records, and having 10000 it can be very slow, so my suggestion is to change slightly the structure, so you won't have to iterate:
Define all the css features of the list on customer-wrapper
class and make it the parent div of all the list elements.
When your ajax request add an element, create a variable containing the name replacing spaces for underscores, let's call it underscore_name.
Add the name to the list as:
var customerHtml = "<div id='"+underscore_name+'>" + name + "</div>";
Each element of the list will have an unique id that will be "almost" the same as the name, and all the elements of the list will be on the same level under customer-wrapper class.
For the search you can take the user input replace spaces for underscores and put in in a variable, for example searchable_id, and using Jquery:
$('#'+searchable_id).siblings().hide();
siblings will hide the other elements on the same level as searchable_id.
The only problem that it could have is if there is a case of two or more repeated names, because it will try to create two or more divs with the same id.
You can check a simple implementation on http://jsfiddle.net/mqpsppxm/
I have the following html that I like to parse through Cheerios.
var $ = cheerio.load('<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;"><div>This works well.</div><div><br clear="none"/></div><div>So I have been doing this for several hours. How come the space does not split? Thinking that this could be an issue.</div><div>Testing next paragraph.</div><div><br clear="none"/></div><div>Im testing with another post. This post should work.</div><div><br clear="none"/></div><h1>This is for test server.</h1></body></html>', {
normalizeWhitespace: true,
});
// trying to parse the html
// the goals are to
// 1. remove all the 'div'
// 2. clean up <br clear="none"/> into <br>
// 3. Have all the new 'empty' element added with 'p'
var testData = $('div').map(function(i, elem) {
var test = $(elem)
if ($(elem).has('br')) {
console.log('spaceme');
var test2 = $(elem).removeAttr('br');
} else {
var test2 = $(elem).removeAttr('div').add('p');
}
console.log(i +' '+ test2.html());
return test2.html()
})
res.send(test2.html())
My end goals are to try and parse the html
remove all the div
clean up <br clear="none"/> and change into <br>
and finally have all the empty 'element' (those sentences with 'div') remove to be added with 'p' sentence '/p'
I try to start with a smaller goal in the above code I have written. I tried to remove all the 'div' (it is a success) but I'm unable to to find the 'br. I been trying out for days and have no head way.
So I'm writing here to seek some help and hints on how can I get to my end goal.
Thank you :D
It's easier than it looks, first you iterate over all the DIV's
$('div').each(function() { ...
and for each div, you check if it has a <br> tag
$(this).find('br').length
if it does, you remove the attribute
$(this).find('br').removeAttr('clear');
if not you create a P with the same content
var p = $('<p>' + $(this).html() + '</p>');
and then just replace the DIV with the P
$(this).replaceWith(p);
and output
res.send($.html());
All together it's
$('div').each(function() {
if ( $(this).find('br').length ) {
$(this).find('br').removeAttr('clear');
} else {
var p = $('<p>' + $(this).html() + '</p>');
$(this).replaceWith(p);
}
});
res.send($.html());
You don't want to remove an attribute you want to remove the tag and so you want to switch removeAttr to remove, like so:
var testData = $('div').map(function(i, elem) {
var test = $(elem)
if ($(elem).has('br')) {
console.log('spaceme');
var test2 = $(elem).remove('br');
} else {
var test2 = $(elem).remove('div').add('p');
}
console.log(i +' '+ test2.html());
return test2.html()
})
Following is the code where I display matched user input in the div but I want to hide the div when there is no match for user input. I can't seem to do it with the following code:
HTML code:
<input id="filter" type="text" placeholder="Enter your filter text here.." onkeyup = "test()" />
<div id="lc"> <p id='placeholder'> </p> </div>
JS code:
// JavaScript Document
s1= new String()
s2= new String()
var myArray = new Array();
myArray[0] = "Football";
myArray[1] = "Baseball";
myArray[2] = "Cricket";
myArray[3] = "Hockey";
myArray[4] = "Basketball";
myArray[5] = "Shooting";
function test()
{
s1 = document.getElementById('filter').value;
var myRegex = new RegExp((s1),"ig");
arraysearch(myRegex);
}
function arraysearch(myRegex)
{
document.getElementById('placeholder').innerHTML="";
for(i=0; i<myArray.length; i++)
{
if (myArray[i].match(myRegex))
{
document.getElementById('lc').style.visibility='visible';
document.getElementById('placeholder').innerHTML += myArray[i] + "<br/>";
}
else
{
document.getElementById('lc').style.visibility='hidden';
}
}
}
Regular expressions are a powerful tool but using them for so trivial a job is often troublesome.First you are using a direct input as regular expression which is never so good.
I copied your code and analyzed the logic you are making many many errors
for(i=0; i<myArray.length; i++)
{
if (myArray[i].match(myRegex))
{
document.getElementById('lc').style.visibility='visible';
document.getElementById('placeholder').innerHTML += myArray[i] + "<br/>";
}
else
{
document.getElementById('lc').style.visibility='hidden';
}
consider your code above, if I enter football, it matches with football, and football is shown. Next it checks for baseball which does not match and visibility changes to hidden!!
Better logic
1.Check what strings match, and add them to the division.
2.Check how many strings have matched, if none, change visibility to hidden.
You are using regular expressions when this actully can be achieved easily with indexOf();
these are pure logical errors
consider using jquery. (with a little http://underscorejs.org/ for utility)
var myArray = ["Football", "Baseball", "Cricket","Hockey", "Basketball", "Shooting"]
$("#filter").keyup(function() {
if(_.include(myArray, $(this).val()) {
$('#lc').show()
} else {
$('#lc').hide()
}
}