I am working on a xamarin.ios application to show the content in a text file into webview. I could able to display the content.
Now I need to add search feature, so that the selected string needs to highlighted and SCROLL need to position in to the search text. I am using below Javascript to highlight the searched text and highlighting is working as expected.
string startSearch = "MyApp_HighlightAllOccurencesOfString('" + searchStr + "')";
this.webView.EvaluateJavascript (startSearch);
How can I move the scroll position to the searched string using this webview?
Thanks in advance
Roshil K
You can use the code snippet to reset the scroll position of the WebView, like this:
webView.ScrollView.ContentOffset = new CGPoint(0,50);
But you have to know the (x,y) point of the related string. I am not familiar with Javascript, maybe it can be returned by your JS code.
Also, I found a solution via JS which maybe help you here:https://stackoverflow.com/a/38317775/5474400.
I got is solved by adding the below javascript to my existing "MyApp_HighlightAllOccurencesOfString" javascript.
var desiredHeight = span.offsetTop - 140;
window.scrollTo(0,desiredHeight);
My Complete Javascript is below.
this.webView.EvaluateJavascript ("// We're using a global variable to store the
number of occurrences
var MyApp_SearchResultCount = 0;
// helper function, recursively searches in elements and their child nodes
function MyApp_HighlightAllOccurencesOfStringForElement(element,keyword) {
if (element) {
if (element.nodeType == 3) { // Text node
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
var span = document.createElement(\"span\");
var text = document.createTextNode(value.substr(idx,keyword.length));
span.appendChild(text);
span.setAttribute(\"class\",\"MyAppHighlight\");
span.style.backgroundColor=\"yellow\";
span.style.color=\"black\";
text = document.createTextNode(value.substr(idx+keyword.length));
element.deleteData(idx, value.length - idx);
var next = element.nextSibling;
element.parentNode.insertBefore(span, next);
element.parentNode.insertBefore(text, next);
element = text;
var desiredHeight = span.offsetTop - 140;
window.scrollTo(0,desiredHeight); MyApp_SearchResultCount++;\t// update the counter
}
} 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--) {
MyApp_HighlightAllOccurencesOfStringForElement(element.childNodes[i],keyword);
}
}
}
}
}
// the main entry point to start the search
function MyApp_HighlightAllOccurencesOfString(keyword) {
MyApp_RemoveAllHighlights();
MyApp_HighlightAllOccurencesOfStringForElement(document.body, keyword.toLowerCase());
}
// helper function, recursively removes the highlights in elements and their childs
function MyApp_RemoveAllHighlightsForElement(element) {
if (element) {
if (element.nodeType == 1) {
if (element.getAttribute(\"class\") == \"MyAppHighlight\") {
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 (MyApp_RemoveAllHighlightsForElement(element.childNodes[i])) {
normalize = true;
}
}
if (normalize) {
element.normalize();
}
}
}
}
return false;
}
// the main entry point to remove the highlights
function MyApp_RemoveAllHighlights() {
MyApp_SearchResultCount = 0;
MyApp_RemoveAllHighlightsForElement(document.body);
}");
Related
I am trying to parse and reformat some web page.
The text is well formatted but the DOM structure is not (generated from WYSIWYG editor).
Thus I would like to parse the text content, then find back corresponding element(s) of each portions of the text.
example problem:
//example.html
<div id="a">
ABC
<span id="b">
DEF
<span id="c">
GHI
</span>
<span id="d">
JKR
</span>
</span>
</div>
//script.js
let a = document.getElementById('a');
let text_pos=a.textContent.indexOf('J');
// good way to get element #d from text_pos?
I know one way is to loop through all child elements of #a, then subtract each text length until 0.
But are there better way?
From what I understood from you is that you want to find parent element of the text that you search for. So instead of looping through all the text we will use indexOf search term and then backtrack to get first tag after that we will forward search to get closing tag and return this part of string between first tag and last tag
Another way is to backtrack to find first id= instead of first html tag but Im not sure if all you elements have id attribute
var data = "<div>Data<div id='d'><br/>AB</div></div>";
console.log(getparentElementOf("AB", data))
function getparentElementOf(searchTerm, data){
var indexOfTerm = data.indexOf(searchTerm);
var indexOfFirstTag = getStartIndexOfParentTag(indexOfTerm);
var indexOfEndTag = getEndIndexOfParentTag(indexOfTerm + searchTerm.length, data.length);
var element = data.substr(0, indexOfEndTag +1);
element = data.substring(indexOfFirstTag, element.length);
return element;
}
function getStartIndexOfParentTag(startFromIndex){
var indexOfFirstTag = -1;
var flagClosingBracket = false, flagOpeningBracket = false;
// back track from that found position until you find the first tag
for(var i = startFromIndex; i >= 0; i--){
// If we have detected closing bracket
if(flagClosingBracket == true){
// If we have / then cancel detected closing bracket
if(data[i] == "/"){
flagClosingBracket = false;
}else if(data[i] == "<"){
// otherwise we have found index of our first tage
flagOpeningBracket = true;
indexOfFirstTag = i;
i = -1; // to exit loop
}
}else{
// Otherwise detect closing bracket
if(data[i] == ">"){
flagClosingBracket = true;
}
}
}
return indexOfFirstTag;
}
function getEndIndexOfParentTag(startFromIndex, to){
var indexOfFirstTag = -1;
var flagClosingBracket = false, flagOpeningBracket = false, flagSlash = false;;
// back track from that found position until you find the first tag
for(var i = startFromIndex; i < to; i++){
// If we have detected closing bracket
if(flagOpeningBracket == true){
// If we have / then cancel detected closing bracket
if(data[i] == ">"){
flagOpeningBracket = false;
}else if(data[i] == "/"){
// otherwise we have found index of our first tage
flagSlash = true;
}
}else{
// Otherwise detect closing bracket
if(data[i] == "<"){
flagOpeningBracket = true;
}
}
if(flagSlash == true)
{
if(data[i] == ">"){
flagClosingBracket = true;
indexOfFirstTag = i;
i = to; // to exit loop
}
}
}
return indexOfFirstTag;
}
Well I think the question is alittle confusing but as I undestoood you you want the text of the elements as they are nested you should loop them. As you comment at the question text. I leave you a fragment of a loop with no lenght evaluation:
var strResult = "";
let a = document.getElementById('a');
for(content_word in a.textContent.trim().split("\n")) {
var isaWord = /[aA-zZ]/.test(a.textContent.trim().split("\n")[content_word])
if (isaWord) {
strResult = strResult + a.textContent.trim().split("\n")[content_word].trim()
}
};
console.log(strResult)
I hope this could help.
Regards
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])
I'm building an interface that consists of 9 cells in table. When a person mouses over a cell, I want other cells to become visible, and change the text content of some of the cells. I can do that just fine if I create individual functions to change the content of each cell, but that's crazy.
I want a single function to change the text depending on the cells involved. I created a function that can take n arguments, and loops through making changes based on the arguments passed in to the function. It doesn't work.
Code for the function is below. If I call it, onMouseOver="changebox('div3')", the argument makes it to the function when I mouse over the cell. If I uncomment the document.write(cell) statement, in this instance, it prints div3 to the screen. So... why isn't it making any changes to the content of the div3 cell?
function changebox() {
for (var i = 0; i < arguments.length; i++) {
var cell = document.getElementById(arguments[i]).id;
var text = "";
if (cell == 'div3') {
text = "Reduced Travel";
} else if (cell == 'div4') {
text = "Reduced Cost";
}
//document.write(cell)
cell.innerHTML = text;
}
}
In your code cell is a string which holds the id of the object. Update the code as follows
function changebox() {
for (var i = 0; i < arguments.length; i++) {
var cell = document.getElementById(arguments[i]),
text = "";
if (cell.id == 'div3') {
text = "Reduced Travel";
} else if (cell.id == 'div4') {
text = "Reduced Cost";
}
//document.write(cell)
cell.innerHTML = text;
}
}
UPDATE :
You can reduce the code as #Tushar suggested.
No need of iterating over arguments(assuming there are only two elements, but can be modified for more elements).
function changebox() {
// As arguments is not real array, need to use call
// Check if div is present in the arguments array
var div3Index = [].indexOf.call(arguments, 'div3') > -1,
div4Index = [].indexOf.call(arguments, 'div4') > -1;
// If present then update the innerHTML of it accordingly
if (div3Index) {
document.getElementById('div3').innerHTML = 'Reduced Travel';
} else if (div4Index) {
document.getElementById('div4').innerHTML = 'Reduced Cost';
}
}
function changebox() {
var args = [].slice.call(arguments);
args.map(document.getElementById.bind(document)).forEach(setElement);
}
function setElement(ele) {
if (ele.id === 'div3') {
ele.innerHTML = "Reduced Travel";
} else if (ele.id === 'div4') {
ele.innerHTML = "Reduced Cost";
}
}
this make your function easy to be tested
As your assigning the cell variable the id of the element and changing the innerHTML of cell which is not valid .
var changeText = function() {
console.log("in change text");
for(var i= 0; i<arguments.length; i++) {
var elem = document.getElementById(arguments[i]);
var cell = document.getElementById(arguments[i]).id;
var text = "";
console.log(cell)
if (cell === "div-1") {
text = cell+" was selected!!";
} else if(cell === "div-3") {
text = cell+" was selected!!";
} else {
text = cell+" was selected";
}
elem.innerHTML = text;
}
}
This would properly change the text of div mouseovered!!
So im trying to remove HTML div's from it's parent div.
I have a div which contains the div that need to be removed, selectedDivs.
However my current function refuses to remove more then 1 item from it's parent div...
Here's what i tried:
Console output: http://pastebin.com/KCeKv1pG
var selectedDivs = new Array();
canvas.innerHTML += "<div id="+currDev+" class='DRAGGABLE' onClick='addBorder(this)>" + "<img src='/devices/" + device + ".gif'></img></div>";
function addBorder(e) {
if (ctrlBeingpressed == true) {
selectedDivs.push(e);
e.style.border = "2px dotted black";
}
}
function deleteSelected() {
console.log(selectedDivs);
var len = selectedDivs.length;
for (var i = 0, len; i < len; i++){
console.log("before html remove: " + selectedDivs.length);
var node = selectedDivs[i];
node.parentNode.removeChild(node);
console.log("after html remove: " + selectedDivs.length);
for (var i in racks)
{
console.log(i);
if(node.id == racks[i].refdev)
{
console.log("Found in rack");
for (z = 1; z < racks[i].punkt.length; z++)
{
if(racks[i].punkt[z] != undefined)
{
if(racks[i].punkt[z].y.indexOf("S") > -1) //If it's an already defined point at an S card
{
//Clearing the TD
$("#sTab tr:eq("+(cardsS.indexOf(racks[i].punkt[z].y)+1)+") td:eq("+(racks[i].punkt[z].x-1)+")").html(" ");
$("#sTab tr:eq("+(cardsS.indexOf(racks[i].punkt[z].y)+1)+") td:eq("+(racks[i].punkt[z].x-1)+")").css("background-color","#E6E6E6");
}
else // Then it must be a P or V card
{
$("#pvTab tr:eq("+(cardsPV.indexOf(racks[i].punkt[z].y)+1)+") td:eq("+(racks[i].punkt[z].x-1)+")").html(" ");
$("#pvTab tr:eq("+(cardsPV.indexOf(racks[i].punkt[z].y)+1)+") td:eq("+(racks[i].punkt[z].x-1)+")").css("background-color","#E6E6E6");
}
}
}
console.log("Found in rack, breaking this loop");
delete racks[i];
break;
}
}
}
As discussed in the comments, there's a problem with resetting the value of the i variable within the nested loop. I took the liberty of editing the code to the way I would write it. I jQueried up some things since you're already using it anyway. (This code assumes you can target IE 9 or later and thus use Array.prototype.forEach and also that racks is an array, which seemed to be the case from the original.)
var selectedDivs = [];
$(canvas).append("<div id="+currDev+" class='DRAGGABLE' onClick='markSelected(this)'><img src='/devices/" + device + ".gif'></img></div>");
function markSelected(div) {
if (ctrlBeingpressed == true) {
selectedDivs.push(div);
$(div).css("border", "2px dotted black");
}
}
function deleteSelected() {
var i, z, deletedDivIDs = [];
console.log(selectedDivs);
selectedDivs.forEach(function(selectedDiv, index, selectedDivs) {
console.log("Removing", selectedDiv, "at index", index);
divIDs.push(selectedDiv.id);
selectedDiv.parentNode.removeChild(selectedDiv);
});
racks.forEach(function(rack, index, racks) {
console.log(i);
if(deletedDivIDs.indexOf(rack.refdev) !== -1) {
console.log("Found in rack");
for (z = 1; z < rack.punkt.length; z++) {
if(rack.punkt[z] !== undefined) {
if(rack.punkt[z].y.indexOf("S") > -1) {//If it's an already defined point at an S card
//Clearing the TD
$("#sTab tr:eq("+(cardsS.indexOf(rack.punkt[z].y)+1)+") td:eq("+(rack.punkt[z].x-1)+")").css("background-color","#E6E6E6").empty();
}
else { // Then it must be a P or V card
$("#pvTab tr:eq("+(cardsPV.indexOf(rack.punkt[z].y)+1)+") td:eq("+(rack.punkt[z].x-1)+")").css("background-color","#E6E6E6").empty();
}
}
}
racks[rack] = undefined;
}
});
}
I didn't have a chance to test this in real code since we still don't know what racks looks like, but hopefully this gets you further down the road.
you have created nested for loops with the same var i=0, It could be your problem.
And the other point I like to point out is, if racks is an array you'd better not use for(var i in racks) because it would scan all other prototype attributes in your Array.prototype, which depends on what libraries you have used in your page. and If racks is not an array, it would scan all other properties in your Object.prototype, what I mean is, if it is just a iteration using for(var i in racks) is not safe, because adding a new Javascript library could mess with your code.
My goal is to count all word in html page as well as count fixed word in html page the prob is that using that function script tag text also get in count so how i remove script tag from counting keywords.
i this code MSO_ContentTable is id 0f div tag. give me any other solution on jquery also if there.
function CountWord(keyword) {
var word = keyword.toUpperCase(),
total = 0,
queue = [document.getElementById('MSO_ContentTable')],
curr, count = 0;
while (curr = queue.pop()) {
var check = curr.textContent;
if (check != undefined) {
for (var i = 0; i < curr.childNodes.length; ++i) {
if (curr.childNodes[i].nodeName == "SCRIPT") {
// do nothing
}
else {
switch (curr.childNodes[i].nodeType) {
case 3: // 3
var myword = curr.childNodes[i].textContent.split(" ");
for (var k = 0; k < myword.length; k++) {
var upper = myword[k].toUpperCase();
if (upper.match(word)) {
count++;
wc++;
}
else if((upper[0] >= 'A' && upper[0] <= 'Z') ||
(upper[0] >= 'a' && upper[0] <= 'z') ||
(upper[0] >= '0' && upper[0] <= '9')) {
wc++
}
}
case 1: // 1
queue.push(curr.childNodes[i]);
}
}
}
}
}
thx
other problem is how i remove the tag which have their display property none?
In your code:
> queue = [document.getElementById('MSO_ContentTable')],
> curr, count = 0;
>
> while (curr = queue.pop()) {
getElementById will only ever return a single node, so no need to put it in an array and no need to pop it later:
curr = document.getElementById('MSO_ContentTable');
if (curr) {
// do stuff
.
> var check = curr.textContent;
The DOM 3 Core textContent property is not supported by all browsers, you need to offer an alternative such as innerText, e.g.:
// Get the text within an element
// Doesn't do any normalising, returns a string
// of text as found.
function getTextRecursive(element) {
var text = [];
var self = arguments.callee;
var el, els = element.childNodes;
for (var i=0, iLen=els.length; i<iLen; i++) {
el = els[i];
// May need to add other node types here
// Exclude script element content
if (el.nodeType == 1 && el.tagName && el.tagName.toLowerCase() != 'script') {
text.push(self(el));
// If working with XML, add nodeType 4 to get text from CDATA nodes
} else if (el.nodeType == 3) {
// Deal with extra whitespace and returns in text here.
text.push(el.data);
}
}
return text.join('');
}
.
> if (check != undefined) {
Given that check will always be a string (even if textContent or innerText are used instead of the above function), testing against undefined doesn't seem appropriate. Also, I don't understand why this test is done before looping over the child nodes.
Anyhow, the getText function above will return the text content without script elements, so you can just use that to get the text then play with it as you want. You may need to normalise whitespace as different browsers will return different amounts.
PS. I should note that arguments.callee is restricted in ES5 strict mode, so if yo plan on using strict mode, replace that expression with an explicit call to the function.
Edit
To exclude not visible elements, you need to test each one to see if it's visible. Only test elements, don't test text nodes as if their parent element is not visible, the text won't be.
Note that the following is not widely tested yet, but works in IE 6 and recent Firefox, Opera and Chrome at least. Please test thoroughly before using more widely.
// The following is mostly from "myLibrary"
// <http://www.cinsoft.net/mylib.html>
function getElementDocument(el) {
if (el.ownerDocument) {
return el.ownerDocument;
}
if (el.parentNode) {
while (el.parentNode) {
el = el.parentNode;
}
if (el.nodeType == 9 || (!el.nodeType && !el.tagName)) {
return el;
}
if (el.document && typeof el.tagName == 'string') {
return el.document;
}
return null;
}
}
// Return true if element is visible, otherwise false
//
// Parts borrowed from "myLibrary"
// <http://www.cinsoft.net/mylib.html>
function isVisible(el) {
if (typeof el == 'string') el = document.getElementById(el);
var doc = getElementDocument(el);
var reVis = /\bhidden\b|\bnone\b/;
var styleObj, isVis;
// DOM compatible
if (doc && doc.defaultView && doc.defaultView.getComputedStyle) {
styleObj = doc.defaultView.getComputedStyle(el, null);
// MS compatible
} else if (el.currentStyle) {
styleObj = el.currentStyle;
}
// If either visibility == hidden || display == none
// then element is not visible
return !reVis.test(styleObj.visibility + ' ' + styleObj.display);
}