Getting currently selected text - javascript

I'm try to get the currently selected text in an input using window.getSelection() but I'm always getting an empty string:
expect(browser.executeScript("return window.getSelection().toString();")).toEqual("test");
Results into:
Expected '' to equal 'test'.
The complete reproducible test using angularjs.org as a target site:
describe("My test", function () {
beforeEach(function () {
browser.get("https://angularjs.org/");
});
it("should select text in an input", function () {
var query = element(by.css("input.search-query"));
query.sendKeys("test");
query.sendKeys(protractor.Key.chord(protractor.Key.COMMAND, "a"));
expect(browser.executeScript("return window.getSelection().toString();")).toEqual("test");
});
});
Note that I actually see the entered text being selected with COMMAND + "a".
What am I doing wrong?
Using protractor 2.5.1, firefox 41.

getSelection does not work for text selected in input elements, but for selections made on elements across the page.
You could use selectionStart and selectionEnd like this:
return document.activeElement.value.substring(
document.activeElement.selectionStart,
document.activeElement.selectionEnd)
You should probably create a function for this instead of this one-liner. And maybe you want to then also test whether document.activeElement is indeed the right type of element, etc. And when you are at it, you might even make it compatible for pre-IE9 browsers... (difficult though)
Simple Function
This will work also on input or textarea controls that do not have focus:
function getInputSelection(el) {
if (el.selectionStart !== undefined) {
return el.value.substring(el.selectionStart, el.selectionEnd);
}
}
// Example call:
console.log(getInputSelection(document.activeElement));
Extensive jQuery Plug-in
This provides for more cross-browser compatibility (pre-IE9), and supports not only getting, but also setting the selection range and text, in the form of a jQuery plug-in. It deals with the fact that CRLF character sequences count as one character position in a pragmatic way (replace in-place by LF only):
/**
* jQuery plug-in for getting/setting the selection range and text
* within input/textarea element(s). When the selection is set,
* the element will receive focus. When getting the selection,
* some browsers require the element to have focus (IE8 and below).
* It is up to the caller to set the focus first, if so needed.
* #this {jQuery} Input/textarea element(s).
* #param {object} opt_bounds When provided, it sets the range as follows:
* #param {number} opt_bounds.start Optional start of the range. If not
* provided, the start point of the range is not altered.
* #param {number} opt_bounds.end Optional end of the range. If not
* provided, the end point of the range is not altered. If null, the end
* of the text value is assumed.
* #param {number} opt_bounds.text Optional text to put in the range. If
* not provided, no change will be made to the range's text.
* #return {jQuery|object|undefined} When setting: the same as #this to
* allow chaining, when getting, an object {start, end, text, length}
* representing the selection in the first element if that info
* is available, undefined otherwise.
*/
$.fn.selection = function (opt_bounds) {
var bounds, inputRange, input, docRange, value;
function removeCR(s) {
// CRLF counts as one unit in text box, so replace with 1 char
// for correct offsetting
return s.replace(/\r\n/g, '\n');
}
if (opt_bounds === undefined) {
// Get
if (!this.length) {
return;
}
bounds = {};
input = this[0];
if (input.setSelectionRange) {
// Modern browsers
bounds.start = input.selectionStart;
bounds.end = input.selectionEnd;
} else {
// Check browser support
if (!document.selection || !document.selection.createRange) {
return;
}
// IE8 or older
docRange = document.selection.createRange();
// Selection must be confined to input only
if (!docRange || docRange.parentElement() !== input) { return; }
// Create another range that can only extend within the
// input boundaries.
inputRange = input.createTextRange();
inputRange.moveToBookmark(docRange.getBookmark());
// Measure how many characters we can go back within the input:
bounds.start =
-inputRange.moveStart('character', -input.value.length);
bounds.end = -inputRange.moveEnd('character', -input.value.length);
}
// Add properties:
bounds.length = bounds.end - bounds.start;
bounds.text = removeCR(input.value).
substr(bounds.start, bounds.length);
return bounds;
}
// Set
if (opt_bounds.text !== undefined) {
opt_bounds.text = removeCR(opt_bounds.text);
}
return this.each(function () {
bounds = $.extend($(this).selection(), opt_bounds);
bounds.end = bounds.end === null ? this.value.length : bounds.end;
if (opt_bounds.text !== undefined) {
value = removeCR(this.value);
this.value = value.substr(0, bounds.start) + bounds.text +
value.substr(bounds.end);
bounds.end = bounds.start + bounds.text.length;
}
if (this.setSelectionRange) {
// Modern browsers
// Call .focus() to align with IE8 behaviour.
// You can leave that out if you don't care about that.
this.focus();
this.setSelectionRange(bounds.start, bounds.end);
} else if (this.createTextRange) {
// IE8 and before
inputRange = this.createTextRange();
inputRange.collapse(true);
inputRange.moveEnd('character', bounds.end);
inputRange.moveStart('character', bounds.start);
// .select() will also focus the element:
inputRange.select();
}
});
};
Example use:
// Get
console.log($('textarea').selection().text);
// Set text
$('textarea').selection({text: "Hello!"});
// Set starting point of selection
$('textarea').selection({start: 1});

Related

Jquery mentions jquery ui autocomplete

I am trying to understand jquery mentions plugin jquery mentionsand i came across this section in the plugin. Can anyone explain what does this mean? What does the last return function does especially?
I want to close the autocomplete dropdown when the matched value has length less than 4
search: function (value, event) {
var match, pos;
//&& value.length >= this.options.minChars
if (!value) {
//sel = window.getSelection();
//node = sel.focusNode;
value = this._value();
pos = Selection.get(this.element).start;
value = value.substring(0, pos);
match = this.matcher.exec(value);
if (!match || match[1].length <= this.options.minChars) {
return '';
}
this.start = match.index;
this.end = match.index + match[0].length;
this.searchTerm = match[1];
//this._setDropdownPosition(node);
}
return $.ui.autocomplete.prototype.search.call(this, this.searchTerm, event);
}
I solved this as i found out that the searchterm was not reset the dropdown wasn't returned a null value. So i changed the search code like this.
search: function (value, event) {
var match, pos;
//&& value.length >= this.options.minChars
if (!value) {
//sel = window.getSelection();
//node = sel.focusNode;
value = this._value();
pos = Selection.get(this.element).start;
value = value.substring(0, pos);
match = this.matcher.exec(value);
if (!match ) {
return '';
}
this.start = match.index;
this.end = match.index + match[0].length;
this.searchTerm = match[1];
if (match[1].length <= this.options.minChars) {//customization: to check minChars
this.searchTerm = '';// customization: to clear autocomplete dropdown
}
this._setDropdownPosition($('.mentions')[0]);
}
return $.ui.autocomplete.prototype.search.call(this,this.searchTerm, event);
}
In this plugin, new jQueryUI widgets are defined - ui.editablecomplete and ui.areacomplete. Both these widgets basically extend the regular autocomplete widget to support <textarea> elements and contenteditable elements.
Whenever you type anything to the inputs (<input type="text">, <textarea> etc) the search method is running. If it passes the all the if statements in the search method, it manipulates the data and calls the last return statement: $.ui.autocomplete.prototype.search.call(this, this.searchTerm, event); which basically tells the autocomplete widget to take over and continue all its actions as usual. This can be analogous to overriding in classical inheritance.
Anyways, if you'd like to open the autocomplete dropdown only for more than 4 characters, you need to change the matcher regular expression. By default, it's /\B[#]([^#]{0,20})$/ which limits the length of the input from 0 to 20 characters. I did not see a way to change it through the API, so I guess you will need to change the code a little bit.
This function defines the matcher:
MentionsBase.prototype._getMatcher = function() {
var allowedChars;
allowedChars = '[^' + this.options.trigger + ']';
return '\\B[' + this.options.trigger + '](' + allowedChars + '{0,20})';
};
You can change the {0,20} to {4,20} (or {5,20} if you want GT and not GTE).
An even better idea, would be to create a pull request to the plugin's author that exposes the match regular expression to the API, instead of changing the code.

Google Sheets custom function conditional formatting

I need to make a cell text turn red if the value of the cell is equal to the value of the cell two rows above it. I have these two functions in my script editor:
/**
* Compares cell value to another cell relative in position to it.
* Returns true if equal values.
*
* #param {Number} colDiff Relative positioning column difference.
* #param {Number} rowDiff Relative positioning row difference.
*/
function equalsRelativeCellValue(rowDiff, colDiff) {
var thisCell = SpreadsheetApp.getActiveRange();
var relativeCellValue = getRelativeCellValue(rowDiff, colDiff);
if (thisCell.getValue() === relativeCellValue)
return true;
else
return false;
}
/**
* Returns value of cell according to relative position
*
* #param {Number} colDiff Relative positioning column difference.
* #param {Number} rowDiff Relative positioning row difference.
*/
function getRelativeCellValue(rowDiff, colDiff) {
var range = SpreadsheetApp.getActiveRange();
var col = range.getColumn();
var row = range.getRow();
var range2 = SpreadsheetApp.getActiveSheet().getRange(row + rowDiff,col + colDiff);
return range2.getValue();
}
The second function, getRelativeCellValue(rowDiff, colDiff) works just fine, I put 8 in a cell, and two cells below it I entered getRelativeCellValue(-2, 0), and the cell evaluated to 8.
But the first function, getRelativeCellValue(rowDiff, colDiff) won't work as my custom function in conditional formatting for some reason:
Custom formula is: =equalsRelativeCellValue(-2,0)
The difficult part is referring to the value of the cell being referenced in the conditional formatting. But my function looks right to me, it returns true if the cell values are equal, and false if they are not. Im hoping I'm just using the "Custom formula is" feature of Conditional Formatting improperly, but the documentation is pretty sparse.
Just realizer what you want to do, just use conditional formatting, select the range, and apply for the first cell, and it will apply for all correctly:
Eg.
In conditional formating dialog, select Custom Formula, paste the custom formula =J10=J8, and select the range J10:J100.
OldAnswer:
You created a circular reference.
If you input =equalsRelativeCellValue(-2,0) in the the Cell, how can it's value be anything, if it is waiting for the function to resolve?
You can overcome this in a column besides the values, or pass the value directly in the function.
You can also use this to make the between cell have a true/false state:
function equalsRelativeCellValue(rowDiff, colDiff) {
var below = getRelativeCellValue(1, 0);
var relativeCellValue = getRelativeCellValue(2, 0);
if (below === relativeCellValue)
return true;
else
return false;
}

Next/previous buttons Javascript/jQuery

In the last few days, I've been stuck in finding a next/previous javascript function for searching a string into a textarea. The function should scroll up or down depending on the search mode.
This textarea contains text that is hard to find manually, so what I would like to do, is having a method for searching a specific string into this textarea to facilitate navigation for the user. I found this nice findNext javascript function by Alan Koontz http://jsfiddle.net/bosshmb/7Tg7d/
/******************************************
* Find In Page Script -- Submitted/revised by Alan Koontz (alankoontz#REMOVETHISyahoo.com)
* Visit Dynamic Drive (http://www.dynamicdrive.com/) for full source code
* This notice must stay intact for use
******************************************/
// revised by Alan Koontz -- May 2003
var TRange = null;
var dupeRange = null;
var TestRange = null;
var win = null;
// SELECTED BROWSER SNIFFER COMPONENTS DOCUMENTED AT
// http://www.mozilla.org/docs/web-developer/sniffer/browser_type.html
var nom = navigator.appName.toLowerCase();
var agt = navigator.userAgent.toLowerCase();
var is_major = parseInt(navigator.appVersion);
var is_minor = parseFloat(navigator.appVersion);
var is_ie = (agt.indexOf("msie") != -1);
var is_ie4up = (is_ie && (is_major >= 4));
var is_not_moz = (agt.indexOf('netscape')!=-1)
var is_nav = (nom.indexOf('netscape')!=-1);
var is_nav4 = (is_nav && (is_major == 4));
var is_mac = (agt.indexOf("mac")!=-1);
var is_gecko = (agt.indexOf('gecko') != -1);
var is_opera = (agt.indexOf("opera") != -1);
// GECKO REVISION
var is_rev=0
if (is_gecko) {
temp = agt.split("rv:")
is_rev = parseFloat(temp[1])
}
// USE THE FOLLOWING VARIABLE TO CONFIGURE FRAMES TO SEARCH
// (SELF OR CHILD FRAME)
// If you want to search another frame, change from "self" to
// the name of the target frame:
// e.g., var frametosearch = 'main'
//var frametosearch = 'main';
var frametosearch = self;
function search(whichform, whichframe) {
// TEST FOR IE5 FOR MAC (NO DOCUMENTATION)
if (is_ie4up && is_mac) return;
// TEST FOR NAV 6 (NO DOCUMENTATION)
if (is_gecko && (is_rev <1)) return;
// TEST FOR Opera (NO DOCUMENTATION)
if (is_opera) return;
// INITIALIZATIONS FOR FIND-IN-PAGE SEARCHES
if(whichform.findthis.value!=null && whichform.findthis.value!='') {
str = whichform.findthis.value;
win = whichframe;
var frameval=false;
if(win!=self)
{
frameval=true; // this will enable Nav7 to search child frame
win = parent.frames[whichframe];
}
}
else return; // i.e., no search string was entered
var strFound;
// NAVIGATOR 4 SPECIFIC CODE
if(is_nav4 && (is_minor < 5)) {
strFound=win.find(str); // case insensitive, forward search by default
// There are 3 arguments available:
// searchString: type string and it's the item to be searched
// caseSensitive: boolean -- is search case sensitive?
// backwards: boolean --should we also search backwards?
// strFound=win.find(str, false, false) is the explicit
// version of the above
// The Mac version of Nav4 has wrapAround, but
// cannot be specified in JS
}
// NAVIGATOR 7 and Mozilla rev 1+ SPECIFIC CODE (WILL NOT WORK WITH NAVIGATOR 6)
if (is_gecko && (is_rev >= 1)) {
if(frameval!=false) win.focus(); // force search in specified child frame
strFound=win.find(str, false, false, true, false, frameval, false);
// The following statement enables reversion of focus
// back to the search box after each search event
// allowing the user to press the ENTER key instead
// of clicking the search button to continue search.
// Note: tends to be buggy in Mozilla as of 1.3.1
// (see www.mozilla.org) so is excluded from users
// of that browser.
if (is_not_moz) whichform.findthis.focus();
// There are 7 arguments available:
// searchString: type string and it's the item to be searched
// caseSensitive: boolean -- is search case sensitive?
// backwards: boolean --should we also search backwards?
// wrapAround: boolean -- should we wrap the search?
// wholeWord: boolean: should we search only for whole words
// searchInFrames: boolean -- should we search in frames?
// showDialog: boolean -- should we show the Find Dialog?
}
if (is_ie4up) {
// EXPLORER-SPECIFIC CODE revised 5/21/03
if (TRange!=null) {
TestRange=win.document.body.createTextRange();
if (dupeRange.inRange(TestRange)) {
TRange.collapse(false);
strFound=TRange.findText(str);
if (strFound) {
//the following line added by Mike and Susan Keenan, 7 June 2003
win.document.body.scrollTop = win.document.body.scrollTop + TRange.offsetTop;
TRange.select();
}
}
else {
TRange=win.document.body.createTextRange();
TRange.collapse(false);
strFound=TRange.findText(str);
if (strFound) {
//the following line added by Mike and Susan Keenan, 7 June 2003
win.document.body.scrollTop = TRange.offsetTop;
TRange.select();
}
}
}
if (TRange==null || strFound==0) {
TRange=win.document.body.createTextRange();
dupeRange = TRange.duplicate();
strFound=TRange.findText(str);
if (strFound) {
//the following line added by Mike and Susan Keenan, 7 June 2003
win.document.body.scrollTop = TRange.offsetTop;
TRange.select();
}
}
}
if (!strFound) alert ("String '"+str+"' not found!") // string not found
}
It works fine, but it only searches for the next string occurence. I don't want to invest more time on writing my own function, because I am not that good in javascript... And that it may be was developed previously by one javascripter. Please let me know if there is a function that meets my requirement.

Best algorithm to highlight a list of given words in an HTML file

I have some HTML files, upon which I have no control. Thus I can't change their structure or markup.
For each of these HTML files, a list of words would be found based on another algorithm. These words should be highlighted in the text of HTML. For example if the HTML markup is:
<p>
Monkeys are going to die soon, if we don't stop killing them.
So, we have to try hard to persuade hunters not to hunt monkeys.
Monkeys are very intelligent, and they should survive.
In fact, they deserve to survive.
</p>
and the list of the words is:
are, we, monkey
the result should be something like:
<p>
<span class='highlight'>Monkeys</span>
<span class='highlight'>are</span>
going to die soon, if
<span class='highlight'>we</span>
don't stop killing them.
So,
<span class='highlight'>we</span>
have to try hard to persuade hunters
not to hunt
<span class='highlight'>monkeys</span>
. They
<span class='highlight'>are</span>
very intelligent, and they should survive.
In fact, they deserve to survive.
</p>
The highlighting algorithm should:
be case-insensitive
be written in JavaScript (this happens inside browser) (jQuery is welcomed)
be fast (be applicable for the text of a given book with almost 800 pages)
not showing browser's famous "stop script" dialog
be applicable for dirty HTML files (like supporting invalid HTML markup, say for example unclosed elements) (some of these files are HTML export of MS Word, and I think you got what I mean by dirty!!!)
should preserve original HTML markup (no markup deletion, no markup change except wrapping intended words inside an element, no nesting change. HTML should look the same before and after edit except highlighted words)
What I've done till now:
I get the list of words in JavaScript in an array like ["are", "we", "monkey"]
I try to select text nodes in the browser (which is faulty now)
I loop over each text node, and for each text node, I loop over each word in the list and try to find it and wrap it inside an element
Please note that you can watch it online here (username: demo#phis.ir, pass: demo). Also current script could be seen at the end of the page's source.
Concatenate your words with | into a string, and then interpret the string as a regex, and then substitute occurences with the full match surrounded by the highlight tags.
The following regular expressions works for your example. Maybe you can pick it up from there:
"Monkeys are going to die soon, if we don't stop killing them. So, we have to try hard to persuade hunters not to hunt monkeys. Monkeys are very intelligent, and they should survive. In fact, they deserve to survive.".replace(/({we|are|monkey[s]?}*)([\s\.,])/gi, "<span class='highlight'>$1</span>$2")
I found the given problem very interessting. Here is what I came up with:
use some plugin (or write one yourself), so that we are able be be notified when an element comes into view
parse that elements text-nodes and wrap each word into a span element using a unqiue css-class name derived from the word itself
add the ability to add css-rules for these unqiue class names
sample: http://jsbin.com/welcome/44285/
The code is very hacky, and only testet in the newest Chrome, but it worked for me and surely can be build upon.
/**
* Highlighter factory
*
* #return Object
*/
function highlighter() {
var
me = {},
cssClassNames = {},
cssClassNamesCount = 0,
lastAddedRuleIndex,
cbCount = 0,
sheet;
// add a stylesheet if none present
if (document.styleSheets.length === 0) {
sheet = document.createElement('style');
$('head').append(sheet);
}
// get reference to the last stylesheet
sheet = document.styleSheets.item(document.styleSheets.length - 1);
/**
* Returns a constant but unique css class name for the given word
*
* #param String word
* #return String
*/
function getClassNameForWord(word) {
var word = word.toLowerCase();
return cssClassNames[word] = cssClassNames[word] || 'highlight-' + (cssClassNamesCount += 1);
}
/**
* Highlights the given list of words by adding a new css rule to the list of active
* css rules
*
* #param Array words
* #param String cssText
* #return void
*/
function highlight(words, cssText) {
var
i = 0,
lim = words.length,
classNames = [];
// get the needed class names
for (; i < lim; i += 1) {
classNames.push('.' + getClassNameForWord(words[i]));
}
// remove the previous added rule
if (lastAddedRuleIndex !== undefined) {
sheet.deleteRule(lastAddedRuleIndex);
}
lastAddedRuleIndex = sheet.insertRule(classNames.join(', ') + ' { ' + cssText + ' }', sheet.cssRules.length);
}
/**
* Calls the given function for each text node under the given parent element
*
* #param DomElement parentElement
* #param Function onLoad
* #param Function cb
* #return void
*/
function forEachTextNode(parentElement, onLoad, cb) {
var i = parentElement.childNodes.length - 1, childNode;
for (; i > -1; i -= 1) {
childNode = parentElement.childNodes[i];
if (childNode.nodeType === 3) {
cbCount += 1;
setTimeout(function (node) {
return function () {
cb(node);
cbCount -= 1;
if (cbCount === 0 && typeof onLoad === 'Function') {
onLoad(me);
}
};
}(childNode), 0);
} else if (childNode.nodeType === 1) {
forEachTextNode(childNode, cb);
}
}
}
/**
* replace each text node by span elements wrapping each word
*
* #param DomElement contextNode
* #param onLoad the parent element
* #return void
*/
function add(contextNode, onLoad) {
forEachTextNode(contextNode, onLoad, function (textNode) {
var
doc = textNode.ownerDocument,
frag = doc.createDocumentFragment(),
words = textNode.nodeValue.split(/(\W)/g),
i = 0,
lim = words.length,
span;
for (; i < lim; i += 1) {
if (/^\s*$/m.test(words[i])) {
frag.appendChild(doc.createTextNode(words[i]));
} else {
span = doc.createElement('span');
span.setAttribute('class', getClassNameForWord(words[i]));
span.appendChild(doc.createTextNode(words[i]));
frag.appendChild(span);
}
}
textNode.parentNode.replaceChild(frag, textNode);
});
}
// set public api and return created object
me.highlight = highlight;
me.add = add;
return me
}
var h = highlighter();
h.highlight(['Lorem', 'magna', 'gubergren'], 'background: yellow;');
// on ready
$(function ($) {
// using the in-view plugin (see the full code in the link above) here, to only
// parse elements that are actual visible
$('#content > *').one('inview', function (evt, visible) {
if (visible) {
h.add(this);
}
});
$(window).scroll();
});
You could try a lib called Linguigi which I hacked together
var ling = new Linguigi();
ling.eachToken(/are|we|monkey/g, true, function(text) {
return '<span class="highlight">' + text + '</span>';
});
If you are using jQuery, Try this.
$('* :not(:has(*))').html(function(i, v) {
return v.replace(/searchString/g, '<span class="highlight">searchString</span>');
});
$('* :not(:has(*))') will search for each node having no child elements and replace the html string in it with given string warapped in your HTML.
My quick and dirtier solution is based on the soultion in this blog:
http://wowmotty.blogspot.in/2011/05/jquery-findreplace-text-without.html
His solution works for a div selector and replaces just the text, mine tries to replace the innerHTML string.
Try this and tell mewhat all can be done. Seem interesting.

jquery filter script not ignoring diacritics and not highlighting string matches as user enters filter text into text input field

I have multiple vocabulary tables on the same html page.
Above each vocabulary table, I would like to enable users to type a word or phrase in a text input field to view only the table rows that contain the typed string (word or phrase). For example, if you type "good" in the text input field, the table rows that do not contain the string "good" will disappear. This is already working if you go to http://www.amanado.com/idioma-colombiano/, click on "Vocabulario (oficial y de jerga) - palabras y frases comunes" to expand the accordion section, and then type "good" in the text input field below the words "Ingresa palabra o frase en el siguiente campo para filtrar la información de la tabla". After typing "good" into the text input field, all but 7 rows in the vocabulary table should disappear (7 rows remain).
I am having the following 3 issues:
1) I am unable to ignore accents (e.g., é, ñ, ü) in the same way that case is already successfully ignored. For example, if a user enters "que" in the input field, rows that contain "que" and "qué" should not disappear. However, when you type "que", rows that contain "qué" do erroneously disappear. As you can see, if you type "que" into the input field (excluding the quotes), 2 records that contain "que" will remain. And, if you type or paste "qué" into the input field (excluding the quotes), 6 records that contain "qué" will remain.
2) I am trying to use a version of jquery.highlight.js to highlight the string matches in the rows that remain/do not disappear. For an example of how this should look visually, type "que" into the input field where instructed in the 2nd paragraph of this question summary and you will see the string "que" is highlighted in the 2 rows that remain/do not disappear. Note that this is not working correctly because I hardcoded the "que" highlight by inserting the script "$("table td").highlight("que");" into the "head" section of the html page for the purposes of demonstrating that (a) jquery.highlight.js is active/functioning and (b) providing a visual example of how the highlighted text is intended to appear.
3) In addition to the javascript that enables users to enter a word or phrase in a field to view only the table rows that contain the entered word or phrase not successfully ignoring accents (e.g., é, ñ, ü), which is the desired behavior, the jquery.highlight.js script is also not successfully ignoring accents (e.g., é, ñ, ü). For example, type "pues" in the input field where instructed in the 2nd paragraph of this question summary and you will multiple cases of the string "Qué" and "qué" not successfully highlighted in the rows that remain/do not disappear. Remember, I hardcoded the "que" highlight by inserting the script "$("table td").highlight("que");" into the section of the html page, so the strings "que", "qué", "Que" and "Qué" should all be highlighted in the table rows that remain/do not disappear if any of the strings "que", "qué", "Que" or "Qué" are entered into the input field given it is intended that (a) case and (b) accents (e.g., é, ñ, ü) are ignored. It is interesting to note that functionionality to "ignoreAccents" is included in the version of jquery.highlight.js that I am using.
Below are:
(a) the input field as it appears in my html;
(b) the javascript I am using to enable users to enter a word or phrase in a field to view only the table rows that contain the entered word or phrase (for the purpose of brevity, this is referred to below as "filter javascript"); and
(c) the version of jquery.highlight.js javascript I am using to highlight text.
Please note: I am not a software engineer, but I do know how to implement a change if someone tells me what to do specifically (e.g., make this exact change, then make this exact change, then make this exact change). I appreciate any assistance anyone can provide and literal instructions would be especially appreciated. And, It is always my intent to use the least amount of code (e.g., javascript, css, html) to achieve the most.
Additional notes/considerations are included at the bottom of this question summary.
(a) input field starts here
<form class="live-search" action="" method="post">
<p>Ingresa palabra o frase en el siguiente campo para filtrar la información de la tabla</p>
<input class="input-text-tr" type="text" value="Mostrar sólo filas que contengan..." />
<span class="filter-count-tr"></span>
</form>
(a) input field ends here
(b) filter javascript starts here
$(function() {
$(".input-text-tr").on('keyup', function(e) {
var disallow = [37, 38, 39, 40];//ignore arrow keys
if($.inArray(e.which, disallow) > -1) {
return true;
}
var inputField = this,
val = this.value,
pattern = new RegExp(val, "i"),
$group = $(this).closest(".group"),
$trs = $group.find(".myTable tbody tr"),
$s;
if(val === '') {
$s = $trs;
}
else {
$s = $();
$trs.stop(true,true).each(function(i, tr) {
if(val !== inputField.value) {//if user has made another keystroke
return false;//break out of .each() and hence out of the event handler
}
$tr = $(tr);
if ($tr.text().match(pattern)) {
$s = $s.add(tr);
}
});
//$trs.not($s).fadeOut();
$trs.not($s).hide();
}
$group.find(".filter-count-tr").text("(" + $s.show ().length + ")");
}).on('focus blur', function() {
if (this.defaultValue == this.value) this.value = '';
else if (this.value == '') this.value = this.defaultValue;
});
$(".group").each(function() {
$this = $(this);
$this.find(".filter-count-tr").text("(" + $this.find("tbody tr").length + ")");
});
});
(b) filter javascript ends here
(c) jquery.highlight.js javascript starts here
jQuery.extend({
highlight: function (node, re, nodeName, className, ignoreAccents) {
if (node.nodeType === 3) {
var nodeData = node.data;
if (ignoreAccents) {
nodeData = jQuery.removeDiacratics(nodeData);
}
var match = nodeData.match(re);
if (match) {
var highlight = document.createElement(nodeName || 'span');
highlight.className = className || 'highlight'; var wordNode = node.splitText(match.index);
wordNode.splitText(match[0].length);
var wordClone = wordNode.cloneNode(true);
highlight.appendChild(wordClone);
wordNode.parentNode.replaceChild(highlight, wordNode);
return 1; //skip added node in parent
}
} else if ((node.nodeType === 1 && node.childNodes) && // only element nodes that have children
!/(script|style)/i.test(node.tagName) && // ignore script and style nodes
!(node.tagName === nodeName.toUpperCase() &&
node.className === className)) { // skip if already highlighted
for (var i = 0; i < node.childNodes.length; i++) {
i += jQuery.highlight(node.childNodes[i], re, nodeName, className, ignoreAccents);
}
}
return 0;
},
removeDiacratics : function(str) {
var rExps = [
{re:/[\xC0-\xC6]/g, ch:'A'},
{re:/[\xE0-\xE6]/g, ch:'a'},
{re:/[\xC8-\xCB]/g, ch:'E'},
{re:/[\xE8-\xEB]/g, ch:'e'},
{re:/[\xCC-\xCF]/g, ch:'I'},
{re:/[\xEC-\xEF]/g, ch:'i'},
{re:/[\xD2-\xD6]/g, ch:'O'},
{re:/[\xF2-\xF6]/g, ch:'o'},
{re:/[\xD9-\xDC]/g, ch:'U'},
{re:/[\xF9-\xFC]/g, ch:'u'},
{re:/[\xD1]/g, ch:'N'},
{re:/[\xF1]/g, ch:'n'}
];
for (var i = 0, len = rExps.length; i < len; i++) {
str = str.replace(rExps[i].re, rExps[i].ch);
}
return str;
}
});
jQuery.fn.unhighlight = function (options) {
var settings = { className: 'highlight', element: 'span' };
jQuery.extend(settings, options);
return this.find(settings.element + "." + settings.className).each(
function () {
var parent = this.parentNode;
parent.replaceChild(this.firstChild, this);
parent.normalize();
}).end();
};
jQuery.fn.highlight = function (words, options) {
var settings = { className: 'highlight', element: 'span', caseSensitive: false, wordsOnly: false, ignoreAccents : true };
jQuery.extend(settings, options);
if (words.constructor === String) {
words = [words];
}
words = jQuery.grep(words, function(word, i) {
return word != '';
});
words = jQuery.map(words, function(word, i) {
return word.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
});
if (words.length == 0) {
return this;
}
var flag = settings.caseSensitive ? "" : "i";
var pattern = "(" + words.join("|") + ")";
if (settings.wordsOnly) {
pattern = "\\b" + pattern + "\\b";
}
var re = [];
re.push(new RegExp(pattern, flag));
if (settings.ignoreAccents) {
var wordsNoAccents = jQuery.map(words, function(word, i) {
return jQuery.removeDiacratics(word);
});
var patternNoAccents;
if (settings.wordsOnly) {
// workaround for word separation using \\b
patternNoAccents = "( " + wordsNoAccents.join("|") + " )";
patternNoAccents = "\\b" + patternNoAccents + "\\b";
} else {
patternNoAccents = "(" + wordsNoAccents.join("|") + ")";
}
if (patternNoAccents!=pattern) {
re.push(new RegExp(patternNoAccents, flag));
}
}
return this.each(function () {
for (var i in re) {
jQuery.highlight(this, re[i], settings.element, settings.className, settings.ignoreAccents);
}
});
};
(c) jquery.highlight.js javascript ends here
Additional notes/considerations start here
1) It is my intent to enhance, not depart from, the javascript I am already using to enable users to enter a word or phrase in a field to view only the table rows that contain the entered word or phrase because the javascript I am already using is working with the exception of the above issues (thanks to Beetroot's excellent contributions to a previous question I published).
2) javascript I've found that touches on the functionality I am trying to achieve includes the following 4 examples (note because stackoverflow does not allow me to use more than a couple links in a question, I replaced "http://" with "[http:// here]" in the below examples):
a) [http:// here]demopill.com/jquery-onpage-text-highlighter-and-filter.html [most closely resembles functionality I am trying to achive; seems to successfully filter and highlight as a user enters text into an input field; successfully ignores case, but does not successfully ignore accents (e.g., é, ñ, ü)];
b) [http:// here]stackoverflow.com/search?q=jquery.highlight.js (dialogue on stackoverflow re: ignoring accented characters)
c) [http:// here]www.jquery.info/The-plugin-SearchHighlight (includes a highlight feature); and
d) [http:// here]docs.jquery.com/UI/Effects/Highlight (includes a highlight feature; note that I am already using "jquery ui" on the website referenced in paragraph 2 of this question summary).
Additional notes/considerations end here
Highlighting
With jquery.highlight.js installed on the page ...
change :
$group.find(".filter-count-tr").text("(" + $s.show().length + ")");
to :
$group.find(".filter-count-tr").text("(" + $s.show().unhighlight().highlight(val).length + ")");
However, the accent-insensitivity code below modifies this.
Accent Insensitivity
This seemed almost impossible but I had a breakthrough on finding this which indicates how the hightlight plugin might be modified to offer accent-insensitive highlighting.
To better understand the code, I refactored it into a better plugin (better for me anyway). It now puts no members into the jQuery namespace (previously one) and one member into jQuery.fn (previously two). With the new plugin, setting and unsetting highlights is performed as follows:
$(selector).highlight('set', words, options);
$(selector).highlight('unset', options);
Explanations and further examples are provided with the code (see below).
The 'set' settings include an '.accentInsensitive' option, which operates (I regret) on a limited number of hard-coded (Spanish) accented character groups implemented about as efficiently as I can manage using a private member in the plugin to cache reusable RegExps and replacement strings for later use by the 'set' method. It would be far better to have a generalized "Unicode normalisation" solution but that's something for another day.
The new plugin also afforded the opportunity to split out part of the code as a separate method, .makepattern, with the advantage that RegExp-ready patterns can be used externally, outside the plugin, with provision for them to be reinjected. This feature allows us to use the plugin as a resource for achieving the other aim here - namely accent-insensitive filtering - with absolute certainty that the RegExp patterns used (for highlighting and filtering) are identical.
Here's the plugin code :
/*
* jQuery highlightIt plugin
* by Beetroot-Beetroot
* https://stackoverflow.com/users/1142252/beetroot-beetroot
*
* based on Highlight by Bartek Szopka, 2009
* http://bartaz.github.com/sandbox.js/jquery.highlight.html,
* based on highlight v3 by Johann Burkard
* http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html
*
* Most important changes:
* - Code refactored into jQuery preferred plugin pattern.
* - Now called with :
* - $(slector).highlight('set', words, options); previously $(slector).highlight(words, options);
* - $(slector).highlight('unset', options); previously $(slector).unhighlight(options);
* - $().highlight('makePattern', words, options); This new option returns a RegExp-ready pattern that can be used externally and/or re-injected for reuse (see .isPattern option below), thus avoiding remaking the pattern as might otherwise happen.
* - 'set' .isPattern option; When true, this new option indicates that the 'words' parameter is a prepared RegExp-ready pattern.
* - 'set' .accentInsensitive option; This new option is limited to operating on hard-coded character groups (eg, Spanish accented chars), not Unicode normalized (which would be a better approach but much harder to achieve and probably slower).
*
* Usage:
* // wrap every occurrance of text 'lorem' in content
* // with <span class='highlight'> (default options)
* $('#content').highlight('set', 'lorem');
*
* // search for and highlight more terms at once
* // so you can save some time on traversing DOM
* $('#content').highlight(['set', 'lorem', 'ipsum']);
* $('#content').highlight('set', 'lorem ipsum');
*
* // search only for entire word 'lorem'
* $('#content').highlight('set', 'lorem', { wordsOnly: true });
*
* // don't ignore case during search of term 'lorem'
* $('#content').highlight('set', 'lorem', { caseSensitive: true });
*
* // wrap every occurrance of term 'ipsum' in content
* // with <em class='important'>
* $('#content').highlight('set', 'ipsum', { element: 'em', className: 'important' });
*
* // remove default highlight
* $('#content').highlight('unset');
*
* // remove custom highlight
* $('#content').highlight('unset', { element: 'em', className: 'important' });
*
* // get accent-insensitive pattern
* $().highlight('makePattern', { element: 'lorem', {'accentInsensitive':true});
*
*
* Copyright (c) 2009 Bartek Szopka
*
* Licensed under MIT license.
*
*/
(function($) {
// **********************************
// ***** Start: Private Members *****
var pluginName = 'highlight';
var accentedForms = [//Spanish accednted chars
//Prototype ...
//['(c|ç)', '[cç]', '[CÇ]', new RegExp('(c|ç)','g'), new RegExp('(C|Ç)','g')],
['(a|á)', '[aá]'],
['(e|é)', '[eé]'],
['(i|í)', '[ií]'],
['(n|ñ)', '[nñ]'],
['(o|ó)', '[oó]'],
['(u|ú|ü)', '[uúü]']
];
//To save a lot of hard-coding and a lot of unnecessary repetition every time the "set" method is called, each row of accentedForms is now converted to the format of the prototype row, thus providing reusable RegExps and corresponding replacement strings.
//Note that case-sensitivity is established later in the 'set' settings so we prepare separate RegExps for upper and lower case here.
$.each(accentedForms, function(i, af) {
af[2] = af[1].toUpperCase();
af[3] = new RegExp(af[0], 'g');
af[4] = new RegExp(af[0].toUpperCase(), 'g');
});
var h = function(node, re, settings) {
if (node.nodeType === 3) {//text node
var match = node.data.match(re);
if (match) {
var wordNode = node.splitText(match.index);
wordNode.splitText(match[0].length);
$(wordNode).wrap($("<" + settings.element + ">").addClass(settings.className));
return 1;
}
} else if ((node.nodeType === 1 && node.childNodes) && // only element nodes that have children
!/(script|style)/i.test(node.tagName) && // ignore script and style nodes
!(node.tagName === settings.element.toUpperCase() && node.className === settings.className)) { // skip if already highlighted
for (var i = 0; i < node.childNodes.length; i++) {
i += h(node.childNodes[i], re, settings);
}
}
return 0;
};
// ***** Fin: Private Members *****
// ********************************
// *********************************
// ***** Start: Public Methods *****
var methods = {
//This is a utility method. It returns a string, not jQuery.
makePattern: function (words, options) {
var settings = {
'accentInsensitive': false
};
$.extend(settings, options || {});
if (words.constructor === String) {
words = [words];
}
words = $.grep(words, function(word, i) {
return word != '';
});
words = $.map(words, function(word, i) {
return word.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
});
if (words.length == 0) { return ''; };
var pattern = "(" + words.join("|") + ")";
if (settings.accentInsensitive) {
$.each(accentedForms, function(i, af) {
pattern = pattern.replace(af[3], af[1]).replace(af[4], af[2]);
});
}
return pattern;
},
set: function (words, options) {
var settings = {
'className': 'highlight',
'element': 'span',
'caseSensitive': false,
'wordsOnly': false,
'accentInsensitive': false,
'isPattern': false
};
$.extend(settings, options || {});
var pattern = settings.isPattern ? words : methods.makePattern(words, settings);
if (pattern === '') { return this; };
if (settings.wordsOnly) {
pattern = "\\b" + pattern + "\\b";
}
var flag = settings.caseSensitive ? "" : "i";
var re = new RegExp(pattern, flag);
return this.each(function () {
h(this, re, settings);
});
},
unset: function (options) {
var settings = {
className: 'highlight',
element: 'span'
}, parent;
$.extend(settings, options || {});
return this.find(settings.element + "." + settings.className).each(function () {
parent = this.parentNode;
parent.replaceChild(this.firstChild, this);
parent.normalize();
}).end();
}
};
// ***** Fin: Public Methods *****
// *******************************
// *****************************
// ***** Start: Supervisor *****
$.fn[pluginName] = function( method ) {
if ( methods[method] ) {
return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || !method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist in jQuery.' + pluginName );
}
};
// ***** Fin: Supervisor *****
// ***************************
})( jQuery );
And here's the application code for the language site :
$(function() {
$(".text-input").on('keyup', function(e) {
var disallow = [37, 38, 39, 40];//ignore arrow keys
if($.inArray(e.which, disallow) > -1) {
return true;
}
var $group = $(this).closest(".group"),
accent_sensitive = false,
case_sensitive = false,
val = this.value,
pattern = $().highlight('makePattern', val, {
'accentInsensitive': !accent_sensitive,
'caseSensitive': case_sensitive
}),
$trs = $group.find(".myTable tbody tr"),
$s;
if(val === '') {
$s = $trs;
}
else {
$s = $();
$trs.stop(true,true).each(function(i, tr) {
$tr = $(tr);
//if ($tr.text().match(new RegExp(pattern, "i"))) {
if ($tr.text().match(new RegExp(pattern, case_sensitive ? '' : "i"))) {
$s = $s.add(tr);
}
});
$trs.not($s).hide();
}
$group.find(".filter-count-tr").text("(" + $s.show().highlight('unset').highlight('set', pattern, {
'isPattern':true,
'caseSensitive':case_sensitive
}).length + ")");
}).on('focus blur', function() {
if (this.defaultValue == this.value) this.value = '';
else if (this.value == '') this.value = this.defaultValue;
});
$(".group").each(function() {
$this = $(this);
$this.find(".filter-count-tr").text("(" + $this.find("tbody tr").length + ")");
});
});
All tested, so should work if installed properly.
By the way, I used this page as my source for Spanish accented characters.

Categories