In JS i need to add span as wrapper on the entire document text words.using below code i can able to add wrapper
function walk(root)
{
if (root.nodeType == 3) // text node
{
doReplace(root);
return;
}
var children = root.childNodes;
for (var i = 0;i<children.length ;i++)
{
walk(children[i]);
}
}
function doReplace(text)
{
var start = counter;
counter = counter+text.nodeValue.length;
var div = document.createElement("div");
var string = text.nodeValue;
var len = string.trim();
if(len.length == 0){
return;
}
var nespan = string.replace(/\b(\w+)\b/g,function myFunction(match, contents, offset, s){
var end = start + contents.length;
var id = start+"_"+end;
start = end;
return "<span class='isparent' id='"+id+"'>"+contents+"</span>";
});
div.innerHTML = nespan;
var parent = text.parentNode;
var children = div.childNodes;
for (var i = children.length - 1 ; i >= 0 ; i--)
{
parent.insertBefore(children[i], text.nextSibling);
}
parent.removeChild(text);
}
using above code ill get below result
input :
Stackoverflow is good
out put:
<span>Stackoverflow</span> <span>is</span> <span>good</span>
expecting result:
<span>Stackoverflow </span><span>is </span><span>good</span>
Add optional space character to your regex after the word but before the second word boundary: \b(\w+\s*)\b
Related
I'm making a text-searching mechanism (like ⌘ + F) for an iOS app and It's working but I have two issues.
Whenever someone searches something in Arabic, the word becomes disconnected.
Users can't search if there are diacritics in the text but their search does not (so basically I'm trying to make it diacritic-insensitive)
Here's the code for my highlighting (which I found from this):
var uiWebview_SearchResultCount = 0;
/*!
#method uiWebview_HighlightAllOccurencesOfStringForElement
#abstract // helper function, recursively searches in elements and their child nodes
#discussion // helper function, recursively searches in elements and their child nodes
element - HTML elements
keyword - string to search
*/
function uiWebview_HighlightAllOccurencesOfStringForElement(element,keyword) {
if (element) {
if (element.nodeType == 3) { // Text node
var count = 0;
var elementTmp = element;
while (true) {
var value = elementTmp.nodeValue; // Search for keyword in text node
var idx = value.toLowerCase().indexOf(keyword);
if (idx < 0) break;
count++;
elementTmp = document.createTextNode(value.substr(idx+keyword.length));
}
uiWebview_SearchResultCount += count;
var index = uiWebview_SearchResultCount;
while (true) {
var value = element.nodeValue; // Search for keyword in text node
var idx = value.toLowerCase().indexOf(keyword);
if (idx < 0) break; // not found, abort
//we create a SPAN element for every parts of matched keywords
var span = document.createElement("span");
var text = document.createTextNode(value.substr(idx,keyword.length));
var spacetxt = document.createTextNode("\u200D");//\u200D
span.appendChild(text);
span.appendChild(spacetxt);
span.setAttribute("class","uiWebviewHighlight");
span.style.backgroundColor="#007DC8a3";
span.style.borderRadius="3px";
index--;
span.setAttribute("id", "SEARCH WORD"+(index));
//span.setAttribute("id", "SEARCH WORD"+uiWebview_SearchResultCount);
//element.parentNode.setAttribute("id", "SEARCH WORD"+uiWebview_SearchResultCount);
//uiWebview_SearchResultCount++; // update the counter
text = document.createTextNode(value.substr(idx+keyword.length));
element.deleteData(idx, value.length - idx);
var next = element.nextSibling;
//alert(element.parentNode);
element.parentNode.insertBefore(span, next);
element.parentNode.insertBefore(text, next);
element = text;
}
} else if (element.nodeType == 1) { // Element node
if (element.style.display != "none" && element.nodeName.toLowerCase() != 'select') {
for (var i=element.childNodes.length-1; i>=0; i--) {
uiWebview_HighlightAllOccurencesOfStringForElement(element.childNodes[i],keyword);
}
}
}
}
}
// the main entry point to start the search
function uiWebview_HighlightAllOccurencesOfString(keyword) {
uiWebview_RemoveAllHighlights();
uiWebview_HighlightAllOccurencesOfStringForElement(document.body, keyword.toLowerCase());
}
// helper function, recursively removes the highlights in elements and their childs
function uiWebview_RemoveAllHighlightsForElement(element) {
if (element) {
if (element.nodeType == 1) {
if (element.getAttribute("class") == "uiWebviewHighlight") {
var text = element.removeChild(element.firstChild);
element.parentNode.insertBefore(text,element);
element.parentNode.removeChild(element);
return true;
} else {
var normalize = false;
for (var i=element.childNodes.length-1; i>=0; i--) {
if (uiWebview_RemoveAllHighlightsForElement(element.childNodes[i])) {
normalize = true;
}
}
if (normalize) {
element.normalize();
}
}
}
}
return false;
}
// the main entry point to remove the highlights
function uiWebview_RemoveAllHighlights() {
uiWebview_SearchResultCount = 0;
uiWebview_RemoveAllHighlightsForElement(document.body);
}
function uiWebview_ScrollTo(idx) {
var idkNum = uiWebview_SearchResultCount - idx
var scrollTo = document.getElementById("SEARCH WORD" + idkNum);
if (scrollTo) scrollTo.scrollIntoView();
}
and I also found this that actually does exactly what I want (does not disconnect words and is diacritic-insensitive) but it's in JQuery and I couldn't figure out how to implement it in my code.
Instead of using indexOf, you can convert the string to an NSString and then use range(of:options:):
var range = value.range(of: keyword, options: [.caseInsensitive, .diacriticInsensitive])
(no jquery please)
Given an html input whose value is split by /\s+/g, how would you find the current token that the caret is positioned at?
For instance, if your input value is
abc ab monkey
And you split it, it will become
["abc, "ab", "monkey"]
But if your caret position in the input is here...
abc ab monk^(caret here)ey
How would you determine which token the caret is currently in?
The api I'm looking for would be something like
var currentToken = getPosition(inputEl.value); // { index: 2, token: "monkey" }
I have most of it down, but when I start backtracking inside the input with the left arrow it gets messed up.
http://jsfiddle.net/dlizik/zmbpq5hz/
html
<input id="input" />
<pre id="test"></pre>
cursor at current token: <span id="res"></span>
js
(function($doc) {
"use strict";
var single = /\s/g;
var spacer = /\s+/g;
var disp = $doc.getElementById("test");
var input = $doc.getElementById("input");
var res = $doc.getElementById("res");
function keyListen(e) {
var tokens = this.value.split(spacer);
var tokenLengths = tokens.map(function(i) { return i.length; });
var cumulative = tokenLengths.map(function(i, n) {
return tokenLengths.slice(0, n + 1).reduce(function(a, b) {
return a + b;
});
});
var cursor = caretPos(this);
var currToken = tokens[getPos(cursor, cumulative)];
res.textContent = currToken;
disp.textContent = JSON.stringify(this.value.split(spacer), null, 2);
}
function getPos(curr, arr) {
for (var i = 0; i < arr.length; i++) {
if (curr <= arr[i]) return i;
}
}
function caretPos(el) {
var val = el.value;
var extra = val.match(single);
var whitespace = extra == null ? 0 : extra.length;
var pos = 0;
if ($doc.selection) {
el.focus();
var sel = $doc.selection.createRange();
sel.moveStart('character', -val.length);
pos = sel.text.length;
}
else if (el.selectionStart || el.selectionStart == '0') pos = el.selectionStart;
return (pos - whitespace);
}
input.addEventListener("keyup", keyListen.bind(input), false);
})(document);
Ok so you can just use regular expressions...wasn't that hard now that I think about it. You just have to find the number of words left of the caret position. That will give you the current token index as well as the string itself. However, you just have to account for a caret whose adjacent strings are both empty spaces.
http://jsfiddle.net/dlizik/zmbpq5hz/1/
This is the function you want to run
(function($doc) {
"use strict";
var single = /\s/g;
var tokenize = /[^\s+]/g;
var ledge = /[\s]*[^\s]+[\s]*/g;
var disp = $doc.getElementById("test");
var input = $doc.getElementById("input");
var res = $doc.getElementById("res");
function keyListen(e) {
var tokens = this.value.match(tokenize);
var pos = caretPos(this);
var adj = this.value.substring(pos - 1, pos + 1);
var left = this.value.slice(0, pos + 1).match(ledge).length - 1;
var curr = adj === " " ? null : tokens[left];
res.textContent = curr + "";
disp.textContent = JSON.stringify(this.value.split(spacer), null, 2);
}
function caretPos(el) {
var pos = 0;
if ($doc.selection) {
el.focus();
var sel = $doc.selection.createRange();
sel.moveStart('character', -el.value.length);
pos = sel.text.length;
}
else if (el.selectionStart || el.selectionStart == '0') pos = el.selectionStart;
return (pos);
}
input.addEventListener("keyup", keyListen.bind(input), false);
})(document);
I have this code that sends the values a user have typed in... the information and numbers are sent as an array and pushed into another array, I then display the text in a dynamically created list element and number in a span element...
var button = $('#add');
button.click(function()
{
var filmnamn = $('#name');
var filmnamnet = filmnamn.val();
var betyg = $('#star');
var betyget = betyg.val();
betyget = Number(betyget);
if(filmnamnet == 0)
{
$('#name').css('background-color', 'red');
}
if(betyget == 0)
{
$('#star').css('background-color', 'red');
}
if(betyget == 0 || filmnamnet == 0)
{
alert("Vänligen fyll i fälten korrekt");
}
else
{
var array = new Array();
array.unshift(betyget);
array.unshift(filmnamnet);
film_array.unshift(array);
betyg_array.unshift(array);
updateFilmList();
}
});
var film_list = $("#filmlista");
var film_array = new Array();
function updateFilmList()
{
document.getElementById("name").value = '';
document.getElementById("star").value = 0;
var filmen = film_array[0][0];
var grade = film_array[0][1];
var element = '<li class="lista">' + filmen + '<span class="betyg">'+ grade +'</span></li>';
film_list.append(element);
changeNumber();
}
And at last I have the function that i want to change the number in the span element to the amount of stars that the number shows... this works fine but only for the first created list and span element, when I try to add more listelements the number wont show up as stars, can someone tell me why this happens and point me in the direction to fix the problem?
function changeNumber(){
var elements= document.getElementsByClassName("betyg");
for (var i=0; i<elements.length; i++) {
var element = elements[i];
var length = parseInt(element.innerHTML);
var x=Array(length+1).join("*");
element.innerHTML=x;
}
}
I thought this would be easier, but running into a weird issue.
I want to split the following:
theList = 'firstword:subwordone;subwordtwo;subwordthree;secondword:subwordone;thirdword:subwordone;subwordtwo;';
and have the output be
firstword
subwordone
subwordtwo
subwordthree
secondword
subwordone
thirdword
subwordone
subwordtwo
The caveat is sometimes the list can be
theList = 'subwordone;subwordtwo;subwordthree;subwordfour;'
ie no ':' substrings to print out, and that would look like just
subwordone
subwordtwo
subwordthree
subwordfour
I have tried variations of the following base function, trying recursion, but either get into infinite loops, or undefined output.
function getUl(theList, splitOn){
var r = '<ul>';
var items = theList.split(splitOn);
for(var li in items){
r += ('<li>'+items[li]+'</li>');
}
r += '</ul>';
return r;
}
The above function is just my starting point and obviously doesnt work, just wanted to show what path I am going down, and to be shown the correct path, if this is totally off base.
It seems you need two cases, and the difference between the two is whether there is a : in your string.
if(theList.indexOf(':') == -1){
//Handle the no sublist case
} else {
//Handle the sublist case
}
Starting with the no sublist case, we develop the simple pattern:
var elements = theList.split(';');
for(var i = 0; i < elements.length; i++){
var element = elements[i];
//Add your element to your list
}
Finally, we apply that same pattern to come up with the implementation for the sublist case:
var elements = theList.split(';');
for(var i = 0; i < elements.length; i++){
var element = elements[i];
if(element.indexOf(':') == -1){
//Add your simple element to your list
} else {
var innerElements = element.split(':');
//Add innerElements[0] as your parent element
//Add innerElements[1] as your child element
//Increment i until you hit another element with ':', adding the single elements each increment as child elements.
//Decrement i so it considers the element with the ':' as a parent element.
}
}
Keep track of the current list to add items to, and create a new list when you find a colon in an item:
var baseParent = $('ul'), parent = baseParent;
$.each(theList.split(';'), function(i, e) {
if (e.length) {
var p = e.split(':');
if (p.length > 1) {
baseParent.append($('<li>').append($('<span>').text(p[0])).append(parent = $('<ul>')));
}
parent.append($('<li>').text(p[p.length - 1]));
}
});
Demo: http://jsfiddle.net/Guffa/eWQpR/
Demo for "1;2;3;4;": http://jsfiddle.net/Guffa/eWQpR/2/
There's probably a more elegant solution but this does the trick. (See edit below)
function showLists(text) {
// Build the lists
var lists = {'': []};
for(var i = 0, listKey = ''; i < text.length; i += 2) {
if(text[i + 1] == ':') {
listKey = text[i];
lists[listKey] = [];
} else {
lists[listKey].push(text[i]);
}
}
// Show the lists
for(var listName in lists) {
if(listName) console.log(listName);
for(var j in lists[listName]) {
console.log((listName ? ' ' : '') + lists[listName][j]);
}
}
}
EDIT
Another interesting approach you could take would be to start by breaking it up into sections (assuming text equals one of the examples you gave):
var lists = text.match(/([\w]:)?([\w];)+/g);
Then you have broken down the problem into simpler segments
for(var i = 0; i < lists.length; i++) {
var listParts = lists[i].split(':');
if(listParts.length == 1) {
console.log(listParts[0].split(';').join("\n"));
} else {
console.log(listParts[0]);
console.log(' ' + listParts[1].split(';').join("\n "));
}
}
The following snippet displays the list depending on your requirements
var str = 'subwordone;subwordtwo;subwordthree;';
var a = []; var arr = [];
a = str;
var final = [];
function split_string(a){
var no_colon = true;
for(var i = 0; i < a.length; i++){
if(a[i] == ':'){
no_colon = false;
var temp;
var index = a[i-1];
var rest = a.substring(i+1);
final[index] = split_string(rest);
return a.substring(0, i-2);
}
}
if(no_colon) return a;
}
function display_list(element, index, array) {
$('#results ul').append('<li>'+element+'</li>');
}
var no_colon_string = split_string(a).split(';');
if(no_colon_string){
$('#results').append('<ul><ul>');
}
no_colon_string.forEach(display_list);
console.log(final);
working fiddle here
The following codes doesn't work and the result is broken because there are white spaces in a HTML tag.
HTML:
<div>Lorem ipsum <a id="demo" href="demo" rel="demo">dolor sit amet</a>, consectetur adipiscing elit.</div>
Javascript:
var div = document.getElementsByTagName('div')[0];
div.innerHTML = div.innerHTML.replace(/\s/g, '<span class="space"> </span>');
How to replace replace white spaces which are not in HTML tags?
It would be a better idea to actually use the DOM functions rather than some unreliable string manipulation using a regexp. splitText is a function of text nodes that allows you to split text nodes. It comes in handy here as it allows you to split at spaces and insert a <span> element between them. Here is a demo: http://jsfiddle.net/m5Qe8/2/.
var div = document.querySelector("div");
// generates a space span element
function space() {
var elem = document.createElement("span");
elem.className = "space";
elem.textContent = " ";
return elem;
}
// this function iterates over all nodes, replacing spaces
// with space span elements
function replace(elem) {
for(var i = 0; i < elem.childNodes.length; i++) {
var node = elem.childNodes[i];
if(node.nodeType === 1) {
// it's an element node, so call recursively
// (e.g. the <a> element)
replace(node);
} else {
var current = node;
var pos;
while(~(pos = current.nodeValue.indexOf(" "))) {
var next = current.splitText(pos + 1);
current.nodeValue = current.nodeValue.slice(0, -1);
current.parentNode.insertBefore(space(), next);
current = next;
i += 2; // childNodes is a live array-like object
// so it's necessary to advance the loop
// cursor as well
}
}
}
}
You can deal with the text content of the container, and ignore the markup.
var div = document.getElementsByTagName('div')[0];
if(div.textContent){
div.textContent=div.textContent.replace(/(\s+)/g,'<span class="space"> </span>';
}
else if(div.innerText){
div.innerText=div.innerText.replace(/(\s+)/g,'<span class="space"> </span>';
}
First split the string at every occurrence of > or <. Then fit together all parts to a string again by replacing spaces only at the even parts:
var div = document.getElementsByTagName('div')[0];
var parts = div.innerHTML.split(/[<>]/g);
var newHtml = '';
for (var i = 0; i < parts.length; i++) {
newHtml += (i % 2 == 0 ? parts[i].replace(/\s/g, '<span class="space"> </span>') : '<' + parts[i] + '>');
}
div.innerHTML = newHtml;
Also see this example.
=== UPDATE ===
Ok, the result of th IE split can be different then the result of split of all other browsers. With following workaround it should work:
var div = document.getElementsByTagName('div')[0];
var sHtml = ' ' + div.innerHTML;
var sHtml = sHtml.replace(/\>\</g, '> <');
var parts = sHtml.split(/[<>]/g);
var newHtml = '';
for (var i = 0; i < parts.length; i++) {
if (i == 0) {
parts[i] = parts[i].substr(1);
}
newHtml += (
i % 2 == 0 ?
parts[i].replace(/\s/g, '<span class="space"> </span>') :
'<' + parts[i] + '>'
);
}
div.innerHTML = newHtml;
Also see this updated example.
=== UPDATE ===
Ok, I have completly changed my script. It's tested with IE8 and current firefox.
function parseNodes(oElement) {
for (var i = oElement.childNodes.length - 1; i >= 0; i--) {
var oCurrent = oElement.childNodes[i];
if (oCurrent.nodeType != 3) {
parseNodes(oElement.childNodes[i]);
} else {
var sText = (typeof oCurrent.nodeValue != 'undefined' ? oCurrent.nodeValue : oCurrent.textContent);
var aParts = sText.split(/\s+/g);
for (var j = 0; j < aParts.length; j++) {
var oNew = document.createTextNode(aParts[j]);
oElement.insertBefore(oNew, oCurrent);
if (j < aParts.length - 1) {
var oSpan = document.createElement('span');
oSpan.className = 'space';
oElement.insertBefore(oSpan, oCurrent);
var oNew = document.createTextNode(' ');
oSpan.appendChild(oNew);
}
}
oElement.removeChild(oCurrent);
}
}
}
var div = document.getElementsByTagName('div')[0];
parseNodes(div);
Also see the new example.