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'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!
I have some HTML in my DOM and I want to replace some strings in it, but only if that was not already replaced or that is not a TAG.
All that is based on an Array that contains the string I want to find and the new string I want this to be replace with.
Work in progress: https://jsfiddle.net/u2Lyaab1/23/
UPDATE: The HTML markup is just for simplicity written with ULs in the sample code, BUT it can contain different tags, event different nesting levels
Basically the desiredReplcement works nice (except that it looks in tags too), but I want that to happen on the DOM, not the new string because I want to maintain any other HTML markup in the DOM.
SNIPPET:
var list = [{
original: 'This is',
new: 'New this is'
},
{
original: 'A list',
new: 'New A list'
},
{
original: 'And I want',
new: 'New And I want'
},
{
original: 'To wrap',
new: 'New To wrap'
},
{
original: 'li',
new: 'bold'
},
{
original: 'This',
new: 'New This'
},
{
original: 'strong',
new: 'bold'
}, {
original: 'This is another random tag',
new: 'This is another random tag that should be bold'
}
];
var div = $('.wrap');
var htmlString = div.html();
var index = 0;
list.forEach(function(item, index) {
console.log(index + ' Should replace: "' + item.original + '" with "' + item.new + '"');
//I know that there is something here, but not sure what
index = htmlString.indexOf(item.original);
var expressionLength = index + item.original.length;
var substring = htmlString.substring(index, expressionLength);
var desiredReplcement = substring.replace(item.original, '<strong>' + item.new + '</strong>');
console.log('index', index);
console.log('substring', substring);
console.log('desiredReplcement', desiredReplcement);
//Current implementation in replace looks in the full div, but I just want to replace in the substring mathced above;
var replacement = '<strong>' + item.new + '</strong>';
var newHTML = div.html().replace(item.original, replacement);
div.html(newHTML);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="wrap">
<ul>
<li>This is</li>
<li>A list</li>
<li>And I want</li>
<li>This should not be bold</li>
<li>To wrap</li>
<li>This</li>
<li>strong</li>
<li>li</li>
</ul>
<span><p><em>This is another random tag</em></p></span>
</div>
Your div variable is referencing <div class="wrap">...</div>, therefore your htmlString value is a group of html tags instead of string.
That is the main reason your code is not working as expected.
And therefore I rewrote your implementation.
var list = [
{
original: 'This is',
new: 'New this is'
},
{
original: 'A list',
new: 'New A list'
},
{
original: 'And I want',
new: 'New And I want'
},
{
original: 'To wrap',
new: 'New To wrap'
},
{
original: 'li',
new: 'bold'
},
{
original: 'This',
new: 'New This'
},
{
original: 'strong',
new: 'bold'
}
];
var div = document.getElementsByClassName('wrap')[0].getElementsByTagName('li'); // Getting all <li> elements within <div class="wrap">
Array.prototype.forEach.call(div, function(li, x){ // Borrowing Array's forEach method to be used on HTMLCollection
list.forEach(function(value, i){ // Looping through list
if (value.original === li.innerHTML) // if list[i]['original'] === li[x].innerHTML
li.innerHTML = '<strong>' + value.new + '</strong>';
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="wrap">
<ul>
<li>This is</li>
<li>A list</li>
<li>And I want</li>
<li>This should not be bold</li>
<li>To wrap</li>
<li>This</li>
<li>strong</li>
<li>li</li>
</ul>
</div>
I don't think that jQuery is necessary here.
First, you want to retrieve your container, which in your case will be the .wrap div.
var container = document.querySelector('.wrap');
Then you want to create a recursive function that will loop through an array to search and replace the data provided.
function replacement(containers, data){
if(!data || !data.length)
return;
for(let i=0; i<containers.length; i++){
var container = containers[i];
// Trigger the recursion on the childrens of the current container
if(container.children.length)
replacement(container.children, data);
// Perform the replacement on the actual container
for(let j=0; j<data.length; j++){
var index = container.textContent.indexOf(data[j].original);
// Data not found
if(index === -1)
continue;
// Remove the data from the list
var replace = data.splice(j, 1)[0];
container.innerHTML = container.innerHTML.replace(replace.original, '<strong>' + replace.new + '</strong>');
// Lower the j by 1 since the data array length has been updated
j--;
// Only want to perform one rule
break;
}
}
}
Demo: https://jsfiddle.net/u2Lyaab1/25/
The following code will not replace tags and will do only one replacement for one text node (if there is any match). It looks through the whole structure in a recursive manner and checks the text of the elements.(and it uses the same list you described in your question)
Requirements:
Replace text just in case of exact match => use === instead of indexOf
Replace text only once => remove item from list after use
var div = $('.wrap');
function substitute(htmlElement, substituteStrings){
var childrenElements = htmlElement.children;
if(childrenElements.length !== 0){
for (let i=0;i<childrenElements.length;i++){
substitute(childrenElements[i], substituteStrings);
}
} else {
var htmlString = htmlElement.innerText;
substituteStrings.some(function(item){
if(htmlString == item.original){
htmlElement.innerHTML = htmlString.replace(item.original, '<strong>' + item.new + '</strong>');
substituteStrings.splice(index,1);
return true;
}
});
}
}
substitute(div[0],list);
The basic idea is to use recursion to search through every nested node in the parent node.
My answer (partial answer) has the same results as Zsolt V's, but is a little less elegant.
Zsolt V has checked child nodes, and it can therefore work with innerHTML by using HTML tags. I on the other hand have checked if a node is a textNode, and have built the replacement nodes using the DOM (pure DOM solution) and nodes' textContent property.
var list = [{
original: 'This is',
new: 'New this is'
}, {
original: 'A list',
new: 'New A list'
}, {
original: 'And I want',
new: 'New And I want'
}, {
original: 'To wrap',
new: 'New To wrap'
}, {
original: 'li',
new: 'bold'
}, {
original: 'This',
new: 'New This'
}, {
original: 'strong',
new: 'bold'
}, {
original: 'This is another random tag',
new: 'This is another random tag that should be bold'
}
];
//I want for each expression in this array, to find that expression in array, replace-it and make-it bold with a <strong> tag.
var div = document.getElementsByClassName("wrap")[0];
function processNode(node) {
if (node.nodeName === "#text") {
list.forEach(function(item, index) {
if (node.parentNode && node.textContent.indexOf(item.original) > -1) {
//node.textContent = node.textContent.replace(item.original, item.new);
let untouched = node.textContent.split(item.original);
console.log(untouched);
for (let i = untouched.length - 1; i > 0; i--) {
untouched.splice(i, 0, item.new);
}
console.log(untouched);
for (let i = 0, l = untouched.length; i < l; i++) {
let newNode = i % 2 === 0 ? document.createTextNode("") : document.createElement("strong");
newNode.textContent = untouched[i];
node.parentNode.appendChild(newNode);
}
node.parentNode.removeChild(node);
}
})
} else {
node.childNodes.forEach(function(child, index) {
processNode(child);
})
}
}
processNode(div)
JSFiddle (partial answer)
You write in the comments on Zsolt V's answer that:
but as you can see, the last sentence is replaced differently than the expected in the list
However, the problem is not with the code, but with the ordering of the list array. The problem is that you have replacements that work within one another, i.e. acting on list[7], with list[0]:
"This is another random tag" (list[7] before)
-> "New this is another random tag" (list[7] after applying changes from list[0])
You need to be mindful of the ordering.
In fact, I moved the last item in the list array to the top, and the results are as you've asked for.
var list = [{
original: 'This is another random tag',
new: 'This is another random tag that should be bold'
}, {
original: 'This is',
new: 'New this is'
}, {
original: 'A list',
new: 'New A list'
}, {
original: 'And I want',
new: 'New And I want'
}, {
original: 'To wrap',
new: 'New To wrap'
}, {
original: 'li',
new: 'bold'
}, {
original: 'This',
new: 'New This'
}, {
original: 'strong',
new: 'bold'
}
];
JSFiddle (full answer)
EXT VIEW PAGE
text : ACTIONS,
xtype : 'actioncolumn',
draggable: false,
dataIndex: 'message',
items:
{
[
{
glyph:'xf044#FontAwesome',
name : 'edit_customer',
handler: function(grid, rowIndex, colIndex, item, event, record, row)
{
this.up("customer-list").getController().EditCustomer(record);
},
}
]
}
HTML OUTPUT:
<span role="button" title=""
class="x-action-col-icon x-action-col-glyph x-action-col-6 x-hide-display"
style="font-family:FontAwesome"
data-qtip="Edit Customer"></span>
But i need to get my id here.
If refresh the page that button id will comeing diff number
For Ex : id="tableview-1738"
So how can i get id or any attrbute from html page driectly.
I need to test automate in this element
Ids in ExtJS are dynamic. So you have to use either the label string or the design of your web page to get hold of elements. Here is a code to get element id based on the label and field type.
private static String getID(WebDriver driver, String elementText, String field_type, Integer occurence) throws InterruptedException {
String pageSource = driver.getPageSource();
String regX = "<" + field_type + " id=\"([^\"]+)\" [^>]+>" + elementText + "<\\/" + field_type + ">";
Pattern id = Pattern.compile(regX);
Matcher match = id.matcher(pageSource);
int count = 0;
while (count < occurence) {
count++;
match.find();
switch (field_type) {
case "label":
returnID = match.group(1).replace("label", "input");
break;
case "span":
returnID = match.group(1).replace("btnInnerEl", "btnIconEl");
break;
}
}
return returnID;
}
Fn call to get id of a textfield with label "Password".
element = driver.findElement(By.id(ElementFinder.getID(driver, "Password", "label",1)));
I am exporting table data as a pdf using pdfmake.
Depending if there a value set within the table, based on what is filtered within the Search dropdowns, I want to return that value. Otherwise I want to return ''.
As of now if nothing is set within the cell it will return as 'null' I want to return '' instead of null.
Here is the expression I am using from the html
<td style="background-color: #bfdfff; word-wrap: break-word;" class="routeCheckInGrid">
<span id="ContentPlaceHolder1_RouteCheckIn_Editable1_lblAccidentMcGoal">{{monday.accidentFreeDayMCG}} /day</span>
</td>
Tried creating a function that gives me the value or returns ' '.
scope.drivers is where all the data is set.
$scope.accidentFreeMC = function(monday) {
if ($scope.drivers) {
if ($scope.resultsRolledUp) {
return monday.accidentFreeDayMCG;
} else {
return '';
}
}
}
using it in the table
table: {
body: [
[
$scope.accidentFreeMC + ' /day',
],
]
}
What am i missing? Any help is much appreciated.
UPDATE**
Here is a working example of something similar that I would like to achieve.
Here is a row where depending on what Route is filtered in the search, will show data in the total column. If a specific Route is selected then nothing is shown in the cell.
$scope.wasTheLCPAuditCompleted = function(day) {
if ($scope.drivers) {
if ($scope.resultsRolledUp) {
return day.lcpAuditCompletedCount + '/' + day.dayRouteCount
}
if (!$scope.resultsRolledUp && (!$scope.dataEntryView || $scope.isRollup)) {
return day.lcpAuditCompleted == 'Y' ? 'Yes' : 'No';
} else {
return '';
}
}
}