Regex Wildcard in Angular - javascript

http://jsfiddle.net/f4Zkm/213/
HTML
<div ng-app>
<div ng-controller="MyController">
<input type="search" ng-model="search" placeholder="Search...">
<ul>
<li ng-repeat="name in names | filter:filterBySearch">
{{ name }}
</li>
</ul>
</div>
</div>
app.js
function escapeRegExp(string){
return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
}
function MyController($scope) {
$scope.names = [
'Lolita Dipietro',
'Annice Guernsey',
'Gerri Rall',
'Ginette Pinales',
'Lon Rondon',
'Jennine Marcos',
'Roxann Hooser',
'Brendon Loth',
'Ilda Bogdan',
'Jani Fan',
'Grace Soller',
'Everette Costantino',
'Andy Hume',
'Omar Davie',
'Jerrica Hillery',
'Charline Cogar',
'Melda Diorio',
'Rita Abbott',
'Setsuko Minger',
'Aretha Paige'];
$scope.search = '';
var regex;
$scope.$watch('search', function (value) {
regex = new RegExp('\\b' + escapeRegExp(value), 'i');
});
$scope.filterBySearch = function(name) {
if (!$scope.search) return true;
return regex.test(name);
};
}
From the above example, I have been trying to create a wildcard regex search by using a special character '*' but I haven't been able to loop through the array.
Current output: If the input is di, it showing all the related matches.
Required: What I am trying to get is, if the input is di*/*(any input), it should show all the matches as per the given input.

There are a couple issues with your approach. First, you are escaping * in your escape routine, so it can not be used by the client.
Second, you are not anchoring your lines, so the match can occur anywhere.
To fix, remove the asterisk from the escape function :
function escapeRegExp(string){
return string.replace(/([.+?^=!:${}()|\[\]\/\\])/g, "\\$1");
}
Then in your watch function replace * with .* and add line anchors :
$scope.$watch('search', function (value) {
var escaped = escapeRegExp(value);
var formatted = escaped.replace('*', '.*')
if(formatted.indexOf('*') === -1){
formatted = '.*' + formatted + '.*'
}
regex = new RegExp('^' + formatted + '$', 'im');
});
Here is a fiddle

Related

Vue2 - Method to highlight text with text input

I'm trying to highlight a text when it matches the text entered in a text input.
So if I have this data
data: function() {
return {
names:['John', 'Johan', 'Diego', 'Edson']
searchFilter:''
}
}
And this html:
<input type="text" v-model="searchFilter">
<div v-for="b in names">
<p v-html="highlight(b)"></p>
</div>
If I type "Joh" in the input, I would like to get in my html:
John
Johan
Diego
Edson
<div>
<p><strong>Joh</strong>n</p>
<p><strong>Joh</strong>an</p>
<p>Diego</p>
<p>Edson</p>
</div>
So far, I have written this method, but it highlights all the word, not just the typed characters.
methods: {
highlight(itemToHighlight) {
if(!this.searchFilter) {
return itemToHighlight;
}
return itemToHighlight.replace(new RegExp(this.searchFilter, "ig"), match => {
return '<strong">' + match + '</strong>';
});
}
}
Any advice would be great. Thanks!
Rough proof of concept
You could do something like this:
methods: {
highlight(itemToHighlight) {
if(!this.searchFilter) {
return itemToHighlight;
}
return itemToHighlight.replace(new RegExp(this.searchFilter, "ig"), match => {
return '<strong">' + this.searchFilter + '</strong>' + (match.replace(this.searchFilter, ''));
});
}
}
Essentially, the idea being that you are using the matching search term as a base, then getting the portion that doesn't match by replacing the matched string with nothing ('').
Please note, this wasn't tested, but more of a proof of concept for you. It most likely works.
A working pure JavaScript implementation
function nameMatcher(names, searchTerm) {
return names.reduce((all, current) => {
let reggie = new RegExp(searchTerm, "ig");
let found = current.search(reggie) !== -1;
all.push(!found ? current : current.replace(reggie, '<b>' + searchTerm + '</b>'));
return all;
}, []);
}
let allNames = ['John', 'Johan', 'Deon', 'Ripvan'];
let searchTerm = 'Joh';
console.log(nameMatcher(allNames, searchTerm));
By running the example, you will see that the function nameMatcher correctly replaces the properly matched string within each positive match with the search term surrounded with a <b> element.
A working Vue Implementation
new Vue({
el: ".vue",
data() {
return {
names: ['John', 'Johan', 'Deon', 'Derek', 'Alex', 'Alejandro'],
searchTerm: ''
};
},
methods: {
matchName(current) {
let reggie = new RegExp(this.searchTerm, "ig");
let found = current.search(reggie) !== -1;
return !found ? current : current.replace(reggie, '<b>' + this.searchTerm + '</b>');
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div class="container vue">
<input v-model="searchTerm" placeholder="Start typing here...">
<div v-for="(name, key) in names">
<div v-html="matchName(name)"></div>
</div>
</div>
Let me know if you have any questions! Hope this helps!

How to highlight search input when data rendered?

searchStr is user input search keyword , once i rendered response from server i want to highlight user input searchStr so user can see what is being searched and compare if its part of response. So below code is highlighting whole string response from server in my case i just want to highlight searched string that will be part of response.
Lets assume i have string
info|<n/a>|[routes.event] ########## Message added to processing queue ########## e63637db-aa33-4aed-b5b0-51a0764dc7f1 { workerId: 3, pid: 33029 } and i want to highlight e63637db-aa33-4aed-b5b0-51a0764dc7f1 _id that will be searchStr
main.html
<tr ng-repeat="item in showMessages | filter:searchStr" >
<td >{{item.filename}}</td>
<td class="serverResults" ng-bind-html="item.value | trusted">{{item.value}}</td>
</tr>
ctrl.js
$scope.$on('displaySearchResults',function(e,data){
$scope.searchStr = data.searchStr;
$scope.showMessages = data.messageObj;
})
filters.js
angular.module('App').filter('trusted', ['$sce', function ($sce) {
return function(text,phrase) {
if (phrase) text = text.replace(new RegExp('('+phrase+')', 'gi'),
'<span class="highlighted">$1</span>');
var content = text.toString()
console.log('Content',content);
var data = content.replace(/[|&;$%#"<>()+,]/g, "");
return $sce.trustAsResourceUrl(data);
};
}]);
Here is a working sample to show how you might accomplish the highlighting. It is contrived because I'm just creating an array with a single item, but it illustrates the approach. You want to apply your replacement of reserved characters first because if you apply that after you have inserted the highlighted <span> the < and > characters will be stripped by your replacement regex.
angular.module('app', [])
.controller('ctrl', function($scope) {
$scope.showMessages = [{
value: 'info|<n/a>|[routes.event] ########## Message added to processing queue ########## e63637db-aa33-4aed-b5b0-51a0764dc7f1 { workerId: 3, pid: 33029 }'
}];
$scope.searchStr = 'e63637db-aa33-4aed-b5b0-51a0764dc7f1';
})
.filter('trusted', function($sce) {
return function(text, phrase) {
if (phrase) {
var data = text.replace(/[|&;$%#"<>()+,]/g, "");
data = data.replace(new RegExp('(' + phrase + ')', 'gi'),
'<span class="highlighted">$1</span>');
return $sce.trustAsHtml(data);
}
text = text.replace(/[|&;$%#"<>()+,]/g, "");
return $sce.trustAsHtml(text);
};
});
.highlighted {
background-color: yellow;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.2/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<div>Search: <input type="text" ng-model="searchStr" /></div>
<div>
<table>
<tr ng-repeat="item in showMessages | filter:searchStr">
<td>{{item.filename}}</td>
<td class="serverResults" ng-bind-html="item.value | trusted:searchStr"></td>
</tr>
</table>
</div>
</div>

How can I dynamically highlight strings on a web page?

I want to create pages with urls such as:
http://xyzcorp/schedules/2015Aug24_Aug28/Jim_Hawkins
http://xyzcorp/schedules/2015Aug24_Aug28/Billy_Bones
http://xyzcorp/schedules/2015Aug24_Aug28/John_Silver
These particular URLs would all contain the exact same content (the "2015Aug24_Aug28" page), but would highlight all instances of the name tagged on to the end. For example, "http://xyzcorp/schedules/2015Aug24_Aug28/Billy_Bones" would show every instance of the name "Billy Bones" highlighted, as if a "Find" for that name was executed on the page via the browser.
I imagine something like this is required, client-side:
var employee = getLastURLPortion(); // return "Billy_Bones" (or whatever)
employee = humanifyTheName(employee); // replaces underscores with spaces, so that it's "Billy Bones" (etc.)
Highlight(employee); // this I have no clue how to do
Can this be done in HTML/CSS, or is JavaScript or jQuery also required for this?
If you call the function
highlight(employee);
this is what that function would look like in ECMAScript 2018+:
function highlight(employee){
Array.from(document.querySelectorAll("body, body *:not(script):not(style):not(noscript)"))
.flatMap(({childNodes}) => [...childNodes])
.filter(({nodeType, textContent}) => nodeType === document.TEXT_NODE && textContent.includes(employee))
.forEach((textNode) => textNode.replaceWith(...textNode.textContent.split(employee).flatMap((part) => [
document.createTextNode(part),
Object.assign(document.createElement("mark"), {
textContent: employee
})
])
.slice(0, -1))); // The above flatMap creates a [text, employeeName, text, employeeName, text, employeeName]-pattern. We need to remove the last superfluous employeeName.
}
And this is an ECMAScript 5.1 version:
function highlight(employee){
Array.prototype.slice.call(document.querySelectorAll("body, body *:not(script):not(style):not(noscript)")) // First, get all regular elements under the `<body>` element
.map(function(elem){
return Array.prototype.slice.call(elem.childNodes); // Then extract their child nodes and convert them to an array.
})
.reduce(function(nodesA, nodesB){
return nodesA.concat(nodesB); // Flatten each array into a single array
})
.filter(function(node){
return node.nodeType === document.TEXT_NODE && node.textContent.indexOf(employee) > -1; // Filter only text nodes that contain the employee’s name.
})
.forEach(function(node){
var nextNode = node.nextSibling, // Remember the next node if it exists
parent = node.parentNode, // Remember the parent node
content = node.textContent, // Remember the content
newNodes = []; // Create empty array for new highlighted content
node.parentNode.removeChild(node); // Remove it for now.
content.split(employee).forEach(function(part, i, arr){ // Find each occurrence of the employee’s name
newNodes.push(document.createTextNode(part)); // Create text nodes for everything around it
if(i < arr.length - 1){
newNodes.push(document.createElement("mark")); // Create mark element nodes for each occurrence of the employee’s name
newNodes[newNodes.length - 1].innerHTML = employee;
// newNodes[newNodes.length - 1].setAttribute("class", "highlighted");
}
});
newNodes.forEach(function(n){ // Append or insert everything back into place
if(nextNode){
parent.insertBefore(n, nextNode);
}
else{
parent.appendChild(n);
}
});
});
}
The major benefit of replacing individual text nodes is that event listeners don’t get lost. The site remains intact, only the text changes.
Instead of the mark element you can also use a span and uncomment the line with the class attribute and specify that in CSS.
This is an example where I used this function and a subsequent highlight("Text"); on the MDN page for Text nodes:
(The one occurrence that isn’t highlighted is an SVG node beyond an <iframe>).
I used the following regex to replace all the matching url to create anchors with highlighted text:
(http://xyzcorp/schedules/(.*?)/)(.*?)( |<|\n|\r|$)
Debuggex Demo
The following code will replace all plain urls. If you don't need them to be replaced to links, just highlight them, remove the tags:
var str = "http://xyzcorp/schedules/2015Aug24_Aug28/Jim_Hawkins http://xyzcorp/schedules/2015Aug24_Aug28/Billy_Bones http://xyzcorp/schedules/2015Aug24_Aug28/John_Silver ";
var highlighted = str.replace( new RegExp("(http://xyzcorp/schedules/(.*?)/)(.*?)( |<|\n|\r|$)","g"), "<a href='$1$3'>$1<span style='background-color: #d0d0d0'>$3</span></a>" );
The content of the highlighted string will be:
<a href='http://xyzcorp/schedules/2015Aug24_Aug28/Jim_Hawkins'>http://xyzcorp/schedules/2015Aug24_Aug28/<span style='background-color: #d0d0d0'>Jim_Hawkins</span></a>
<a href='http://xyzcorp/schedules/2015Aug24_Aug28/Billy_Bones'>http://xyzcorp/schedules/2015Aug24_Aug28/<span style='background-color: #d0d0d0'>Billy_Bones</span></a>
<a href='http://xyzcorp/schedules/2015Aug24_Aug28/John_Silver'>http://xyzcorp/schedules/2015Aug24_Aug28/<span style='background-color: #d0d0d0'>John_Silver</span></a>
UPDATE:
This function will replace the matching names from the input text:
function highlight_names( html_in )
{
var name = location.href.split("/").pop().replace("_"," ");
return html_in.replace( new RegExp( "("+name+")", "g"), "<span style='background-color: #d0d0d0'>$1</span>" );
}
One solution would be, after window is loaded, to traverse all nodes recursively and wrap search terms in text nodes with a highlight class. This way, original structure and event subscriptions are not preserved.
(Here, using jquery, but could be done without):
Javascript:
$(function() {
// get term from url
var term = window.location.href.match(/\/(\w+)\/?$/)[1].replace('_', ' ');
// search regexp
var re = new RegExp('(' + term + ')', 'gi');
// recursive function
function highlightTerm(elem) {
var contents = $(elem).contents();
if(contents.length > 0) {
contents.each(function() {
highlightTerm(this);
});
} else {
// text nodes
if(elem.nodeType === 3) {
var $elem = $(elem);
var text = $elem.text();
if(re.test(text)) {
$elem.wrap("<span/>").parent().html(text.replace(re, '<span class="highlight">$1</span>'));
}
}
}
}
highlightTerm(document.body);
});
CSS:
.highlight {
background-color: yellow;
}
$(function() {
// get term from url
//var term = window.location.href.match(/\/(\w+)\/?$/)[1].replace('_', ' ');
var term = 'http://xyzcorp/schedules/2015Aug24_Aug28/Billy_Bones/'.match(/\/(\w+)\/?$/)[1].replace('_', ' ');
// search regexp
var re = new RegExp('(' + term + ')', 'gi');
// recursive function
function highlightTerm(elem) {
var contents = $(elem).contents();
if(contents.length > 0) {
contents.each(function() {
highlightTerm(this);
});
} else {
// text nodes
if(elem.nodeType === 3) {
var $elem = $(elem);
var text = $elem.text();
if(re.test(text)) {
$elem.wrap("<span/>").parent().html(text.replace(re, '<span class="highlight">$1</span>'));
}
}
}
}
highlightTerm(document.body);
});
.highlight {
background-color: yellow;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div>
<div class="post-text" itemprop="text">
<p>I want to create pages with urls such as:</p>
<pre style="" class="default prettyprint prettyprinted">
<code>
<span class="pln">http</span>
<span class="pun">:</span>
<span class="com">//xyzcorp/schedules/2015Aug24_Aug28/Jim_Hawkins</span>
<span class="pln">
http</span>
<span class="pun">:</span>
<span class="com">//xyzcorp/schedules/2015Aug24_Aug28/Billy_Bones</span>
<span class="pln">
http</span>
<span class="pun">:</span>
<span class="com">//xyzcorp/schedules/2015Aug24_Aug28/John_Silver</span>
</code>
</pre>
<p>These particular URLs would all contain the exact same content (the "2015Aug24_Aug28" page), but would highlight all instances of the name tagged on to the end. For example, " <code>http://xyzcorp/schedules/2015Aug24_Aug28/Billy_Bones</code>
" would show every instance of the name "Billy Bones" highlighted, as if a "Find" for that name was executed on the page via the browser.</p>
<p>I imagine something like this is required, client-side:</p>
<pre style="" class="default prettyprint prettyprinted">
<code>
<span class="kwd">var</span>
<span class="pln"> employee </span>
<span class="pun">=</span>
<span class="pln"> getLastURLPortion</span>
<span class="pun">();</span>
<span class="pln"></span>
<span class="com">// return "Billy_Bones" (or whatever)</span>
<span class="pln">
employee </span>
<span class="pun">=</span>
<span class="pln"> humanifyTheName</span>
<span class="pun">(</span>
<span class="pln">employee</span>
<span class="pun">);</span>
<span class="pln"></span>
<span class="com">// replaces underscores with spaces, so that it's "Billy Bones" (etc.)</span>
<span class="pln"></span>
<span class="typ">Highlight</span>
<span class="pun">(</span>
<span class="pln">employee</span>
<span class="pun">);</span>
<span class="pln"></span>
<span class="com">// this I have no clue how to do</span>
</code>
</pre>
<p>Can this be done in HTML/CSS, or is JavaScript or jQuery also required for this?</p>
</div>
Demo: http://plnkr.co/edit/rhfqzWThLTu9ccBb1Amy?p=preview

Multiple custom filters in AngularJS

I want to put 2 customs filters on a data. Each filter is working perfectly independently, but when I put the 2 on the same data, I have an error message (TypeError: input.replace is not a function), but if I comment this, I have another error message (Error: [$sce:itype] Attempted to trust a non-string value in a content requiring a string: Context: html)
The 2 customs filters are goBold which take no argument, and limitHellip which takes the maximum length of the string as an argument.
thanks a lot, here is the code :
angular.module('appFilters', []).
filter('goBold', function($sce) {
return function (input) {
input = input.replace(' filter ',' <strong>WORD FILTERED</strong> ');
return $sce.trustAsHtml( input );
}
}).
filter('limitHellip', function($sce) {
return function (input, limit) {
console.log(limit);
if( input.length > 100 ) {
input = input.substring(0,100);
var index = input.lastIndexOf(" ");
input = input.substring(0,index) + "…";
}
return $sce.trustAsHtml( input );
}
});
<ion-content class="has-subheader">
<ion-item class="element item item-divider" data-ng-repeat="item in blocInfos" >
<a href="#/list/{{ item.url }}">
<img data-ng-src="img/{{ item.imageUrl }}" alt="">
<h2 class="title">{{ item.title }}</h2>
</a>
<p data-ng-bind-html="item.text | limitHellip: 100 | goBold" class="wrap"></p>
</ion-item>
</ion-content>
This is because your first filter return an $sce.trusAsHtml() which is an Object and not a string so you can't call the replace() method.
So, you have to change it into a String
Plunkr
Filters
angular.module('appFilters', []).
filter('goBold', function($sce) {
return function (input) {
input = input.toString(); //This line was added
input = input.replace(' filter ',' <strong>WORD FILTERED</strong> ');
return $sce.trustAsHtml( input );
}
}).
filter('limitHellip', function($sce) {
return function (input, limit) {
input = input.toString(); //Maybe you have to add it here too
console.log(limit);
if( input.length > limit ) {
input = input.substring(0,limit);
var index = input.lastIndexOf(" ");
input = input.substring(0,index) + "…";
}
return $sce.trustAsHtml( input );
}
})

Angular textarea directive for integers, floats and scientific notation

I have a textarea that holds multiple values(ng-model = array with many positions). And Input(text) for single values(one position array).
I am trying to create a directive(one directive both for the input and the textarea) that allows only to type a value if following pattern is respected:
Allowed characters: numbers, dots, Ee & -
Only one dot and Ee per value
value can not start/end with Ee or dots
a value can start but not end with minus(-)
Consecutive dots, minus or Ee are not allowed
Allowed characters: numbers, dots, Ee & -
Max 2 minus(-) symbols if separated by Ee
Minus symbol can only be at the beginning of a value or right after Ee
As I build the directive its getting dirtier so I am hoping that someone shows me a proper way of doing this.
This is what I have so far but many of the rules I wanna apply are not respected like many(-) symbols consecutive, multiple e in the values etc...
Template multivalue textarea:
<script type="text/ng-template" id="form_field_float">
<textarea only-flo class="form-control" rows="{{dbo.attributes[attobj.name].length + 2}}" ng-model="dbo.attributes[attobj.name]" ng-list="
" ng-trim="false" ng-change="dbo._isDirty = true"></textarea>
</script>
Template single value input:
<script type="text/ng-template" id="form_field_float_single">
<div ng-class="{ 'has-error' : theForm.{{ attobj.name }}_{{ dbo._id['value'] }}_{{ dbo.clazz }}.$invalid && !{{ attobj.name }}_{{ dbo._id['value'] }}_{{ dbo.clazz }}.$pristine }">
<input only-flo type="text" class="form-control" ng-model="dbo.attributes[attobj.name][0]" ng-change="dbo._isDirty = true; forceArray(dbo, attobj);" name="{{ attobj.name }}_{{ dbo._id['value'] }}_{{ dbo.clazz }}" >
</div>
</script>
Directive:
app.directive('onlyFlo', function () {
return {
restrict: 'A',
require: '?ngModel',
link: function (scope, element, attrs, ngModel) {
if (!ngModel) return;
ngModel.$parsers.unshift(function (inputValue) {
//var digits = inputValue.split('').filter(function (s) { return (!isNaN(s) && s != ' ' || s == '.'); }).join('');
var digits= inputValue.replace(/\.{2,}/g, '.').replace(/e{2,}/gmi, 'e').replace(/[^0-9\.\n\r\-e]/gmi, '');
//var digits= inputValue.split(/\s/).filter(function(s){return isNaN(s) ? false : s}).join('\r');
console.log('digits: ' + digits);
var x = String(digits).split("\n");
console.log(x);
for (var i=0; i< x.length; i++){
if (String(x[i]).indexOf(".") !== -1){
console.log('#1 : '+x[i]);
var pos= String(x[i]).indexOf(".");
console.log('pos: ' +pos);
if (String(x[i]).indexOf(".", pos+1) !== -1){
console.log('#2 : '+x[i]);
var pos2= String(x[i]).indexOf(".", pos+1);
console.log('pos2: ' +pos2);
console.log('number before dot delete: ' + x[i]);
x[i].slice(pos2, pos2+1);
x[i] = String(x[i]).slice(0, pos2) + String(x[i].slice(pos2+1, x[i].length +1));
console.log('number after dot delete: ' + x[i]);
}
}
}
digits= x.join("\n");
console.log('digits: ' + digits);
ngModel.$setViewValue(digits);
//the one above (setView...) works better avoids the illegal character to appear on double press
//ngModel.$viewValue = digits;
ngModel.$render();
return digits;
});
}
};
});
Right now what the directive does is:
1 - Replace consecutive dots with one dot. Same with E or e
2 - Remove anything that is not 0-9, -, "\n", "\r", E or e.
3 - Not allow a second dot(a second none consecutive dot)
Obviously this is getting worse as I continue.
I would like a solution that doesn't alter the template side, something that stays within the directive.
I know this is a lot to ask, sorry if I am not explaining my self properly, if so I will try to do better.
The following would be correct values:
Integers:1,2,3...
Floats:1.3, 0.3, 0,03...
Negative integers:-1,-2,-3...
Negative floats:-1.3, -0.3, -0.03...
Scientific notation:3e18, 3E18
Scientific notation:-6e18, -7E18
Scientific notation:-2.2e18, 2.2E-18
Scientific notation:-0.22e18, 2.2E-18
Scientific notation:0.22e-18
Any integer, float or scientific notation represented with e or E.

Categories