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!
Related
I am using the PrimeNG AutoComplete with angular 7.
In the auto complete I return a list of all the
items that their
description contains the search text.
I would like to highlight matched words in each element of the shown list.
<p-autoComplete
#autoComplete
(onFocus)="autoComplete.handleDropdownClick()"
[suggestions]="results"
(completeMethod)="search($event)"
[multiple]="true"
field="Path"
(onSelect)="selectedValues($event)"
(onUnselect)="unSelectedValues($event)"
placeholder="Select Domain"
emptyMessage= {{noResult}}
[formControl]="dataModelControl"
>
<ng-template let-value pTemplate="item">
<div class="ui-helper-clearfix">
<span [innerHTML]="value.Path | highlight :
toHighlight"></span
</div>
</ng-template>
</p-autoComplete>
This is the definition of the highlight pipe:
#Pipe({ name: 'highlight ' })
export class HighlightPipe implements PipeTransform {
transform(text: string, search): string {
const pattern = search
.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&")
.split(' ')
.filter(t => t.length > 0)
.join('|');
const regex = new RegExp(pattern, 'gi');
return search ? text.replace(regex, match => `<b>${match}</b>`) :
text;
}
}
}
//in ts file
search(event) {
if (event.query) {
this.toHighlight = event.query;
this.results = this.data.filter(option => {
return option.Path.toLowerCase().indexOf(event.query.toLowerCase())
>= 0
});
} else {
this.results = this.data.slice();
}
}
I just started to use template literals and tagged template literals. But I'm running into a problem when trying to render a template literal because it renders an extra substitution that I can wonder where it comes from.
This is what I have tried:
My data
var data = {
login: "john_12",
name: "John",
bio: "developer",
email: "jdev#mail.com"
}
My tag function
function replaceNullData(strings, ...parts) {
var checkedMarkup = "";
strings.forEach((string, index) => {
if (!parts[index]){
parts[index] = "data no available";
}
checkedMarkup += string + parts[index];
});
return checkedMarkup;
}
My template literal
var summaryMarkup = replaceNullData`
<div>
<p>Username: ${data.login}</p>
</div>
<div>
<p>Name: ${data.name}</p>
</div>
<div>
<p>Bio: ${data.bio}/<p>
</div>
<div>
<p>Email: ${data.email}</p>
</div>
`;
Now, if I do console.log(summaryMarkup);, I obtain this:
<div>
<p>Username: john_12</p>
</div>
<div>
<p>Name: John</p>
</div>
<div>
<p>Bio: developer/<p>
</div>
<div>
<p>Email: jdev#mail.com</p>
</div>
data no available <------- THIS IS WHAT SHOULDN'T APPEAR
There is an extra "data no available" at the end. It's like the tag function received 6 parts (substitution or expressions) instead of 5.
What am I missing here?
Your parts.length is the length you expected, but notice you're iterating strings, not parts. strings.length === parts.length + 1, so you're accessing parts out-of-bounds. Iterate parts instead and append the last string outside the iteration:
function replaceNullData(strings, ...parts) {
var checkedMarkup = "";
parts.forEach((part, index) => {
if (part == null) { // because false, 0, NaN, and '' should be "available"
part = "data not available";
}
checkedMarkup += strings[index] + part;
});
return checkedMarkup + strings[strings.length - 1]; // manually append last string
}
I'm making a blog and would like the user to be able to create new textareas when they hit enter and for it to autofocus on the newly created textarea. I've tried using the autofocus attribute, but that doesn't work. I've also tried using the nextTick function, but that doesn't work. How do I do this?
<div v-for="(value, index) in content">
<textarea v-model="content[index].value" v-bind:ref="'content-'+index" v-on:keyup.enter="add_content(index)" placeholder="Content" autofocus></textarea>
</div>
and add_content() is defined as follows:
add_content(index) {
var next = index + 1;
this.content.splice(next, 0, '');
//this.$nextTick(() => {this.$refs['content-'+next].contentTextArea.focus()})
}
You're on the right path, but this.$refs['content-'+next] returns an array, so just access the first one and call .focus() on that
add_content(index) {
var next = index + 1;
this.content.splice(next, 0, {
value: "Next"
});
this.$nextTick(() => {
this.$refs["content-" + next][0].focus();
});
}
Working Example
var app = new Vue({
el: '#app',
data() {
return {
content: [{
value: "hello"
}]
};
},
methods: {
add_content(index) {
var next = index + 1;
this.content.splice(next, 0, {
value: "Next"
});
this.$nextTick(() => {
this.$refs["content-" + next][0].focus();
});
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(value, index) in content">
<textarea v-model="content[index].value" v-bind:ref="'content-' + index" v-on:keyup.enter="add_content(index);" placeholder="Content" autofocus></textarea>
</div>
</div>
Also, your value in the array seems to be an object rather than a string, so splice in an object rather than an empty string
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
I´m having some troubles with Meteor Search-source. The pagackage works fine, but I think that it has big leaks in the documentation. My problem right now is that I can´t clear the search when I don´t type any in the search field.
Currently the App show a list of websites. If I looking for some web in the search field, the App show me a list with results. But when I delete the characters in the field text (empty search), the list with results doesn´t disappear. It show the complete list of elements instead of show a empty list.
I have tried a lot of solutions but nothing works...
You can test typing for example "coursera" in the search field in my app, and next delete all types to check it out.
Some suggestion? Many thanks in advance
My App
SERVER
SearchSource.defineSource('items', function(searchText, options) {
var options = {sort: {upvote: -1}, limit: 20};
// var options = options || {};
if(searchText) {
var regExp = buildRegExp(searchText);
/*var selector = {title: regExp, description: regExp};*/
var selector = {$or: [
{title: regExp},
{description: regExp}
]};
return Websites.find(selector, options).fetch();
} else {
return Websites.find({}, options).fetch();
}
});
function buildRegExp(searchText) {
var words = searchText.trim().split(/[ \-\:]+/);
var exps = _.map(words, function(word) {
return "(?=.*" + word + ")";
});
var fullExp = exps.join('') + ".+";
return new RegExp(fullExp, "i");
}
CLIENTE
//search function
var options = {
keepHistory: 1000 * 60 * 5,
localSearch: true
};
var fields = ['title','description'];
itemSearch = new SearchSource('items', fields, options);
//end search function
//search helper
Template.searchResult.helpers({
getItems: function() {
return itemSearch.getData({
transform: function(matchText, regExp) {
return matchText.replace(regExp, "$&")
},
sort: {upvote: -1}
});
},
isLoading: function() {
return itemSearch.getStatus().loading;
}
});
// search events
Template.searchBox.events({
'keyup #search-box': _.throttle(function(e) {
var text = $(e.target).val().trim();
console.log(text);
itemSearch.search(text,{});
}, 200)
});
HTML
<template name="searchResult">
<div class="container">
<div class="jumbotron searchResult">
<h3> Search results </h3>
<ol>
{{#each getItems}}
{{> website_item_search}}
{{/each}}
</ol>
<!--<div id="search-meta">
{{#if isLoading}}
searching ...
{{/if}}
</div>-->
</div>
</div>
</template>
Just by changing the code on server file, you should be able to see no results on blank text field.
Here is new code. https://github.com/ashish1dev/search_source_example
SearchSource.defineSource('packages', function(searchText, options) {
var options = {sort: {isoScore: -1}, limit: 20};
if(searchText.length>=1) {
var regExp = buildRegExp(searchText);
var selector = {$or: [
{packageName: regExp},
{description: regExp}
]};
return Packages.find(selector, options).fetch();
} else if (searchText.length===0){
return [];// return blank array when length of text searched is zero
}
else {
return Packages.find({}, options).fetch();
}
});