MarkText highlights search terms within specified containers using mark.js by Julien Royer.
Type a search term: matches inside the panel are highlighted.
Multiple search terms on the same content, custom styles and combined properties.
The mark AJAX event sends the matched terms and positions to a server-side listener.
The client-side onDone callback fires after mark.js completes marking and receives the total number of matches,
without an AJAX request. Compare with Update Search, where a server-side p:ajax event="mark" listener
receives the matched terms and positions.
<style>
.marktext-highlight,
mark {
background-color: yellow !important;
color: black !important;
}
.custom-highlight {
background-color: lightgreen !important;
color: darkgreen !important;
font-weight: bold;
}
</style>
<div class="card">
<h5>Basic Usage</h5>
<p>Type a search term: matches inside the panel are highlighted.</p>
<h:form id="formBasicUsage">
<h:panelGrid columns="3" cellpadding="4">
<h:outputText value="Search term:"/>
<p:inputText id="searchInput" value="#{markTextController.searchTerm}" placeholder="Enter search term">
<p:ajax event="keyup" delay="500" update="searchContainer markText"/>
</p:inputText>
<p:commandButton value="Highlight" update="searchContainer markText" icon="pi pi-search"/>
</h:panelGrid>
<p:panel id="searchContainer" header="Searchable Content" style="margin-top: 20px">
<h:panelGroup id="searchContent" layout="block">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>PrimeFaces Extensions provides additional components for PrimeFaces. MarkText is a new component that highlights search terms within specified containers using mark.js.</p>
</h:panelGroup>
</p:panel>
<pe:markText id="markText" for="searchContent" value="#{markTextController.searchTerm}" styleClass="marktext-highlight"/>
</h:form>
</div>
<div class="card">
<h5>Advanced Features</h5>
<p>Multiple search terms on the same content, custom styles and combined properties.</p>
<h:form id="formAdvancedFeatures">
<h:panelGrid columns="5" cellpadding="4">
<h:outputText value="Search term 1:"/>
<p:inputText id="searchInput1" value="#{markTextController.searchTerm}" placeholder="Enter first search term">
<p:ajax event="keyup" delay="500" update="advancedPanel markText1 markText2"/>
</p:inputText>
<h:outputText value="Search term 2:"/>
<p:inputText id="searchInput2" value="#{markTextController.searchTerm2}" placeholder="Enter second search term">
<p:ajax event="keyup" delay="500" update="advancedPanel markText1 markText2"/>
</p:inputText>
<p:commandButton value="Highlight" update="advancedPanel markText1 markText2" icon="pi pi-search"/>
</h:panelGrid>
<p:panel id="advancedPanel" header="Advanced Features - Multiple Terms with Custom Styles and Combined Properties">
<h:panelGroup id="advancedContent" layout="block">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</h:panelGroup>
</p:panel>
<pe:markText id="markText1" for="advancedContent" value="#{markTextController.searchTerm}" styleClass="marktext-highlight" caseSensitive="false" separateWordSearch="true" accuracy="partially"/>
<pe:markText id="markText2" for="advancedContent" value="#{markTextController.searchTerm2}" styleClass="#{markTextController.customStyleClass}" caseSensitive="true" separateWordSearch="false" accuracy="exactly"/>
</h:form>
</div>
<div class="card">
<h5>Update Search</h5>
<p>The <i>mark</i> AJAX event sends the matched terms and positions to a server-side listener.</p>
<h:form id="formUpdateSearch">
<p:messages id="messages" showSummary="true" showDetail="true"/>
<h:panelGrid columns="2" cellpadding="4">
<h:outputText value="Search term:"/>
<p:inputText id="searchInput" value="#{markTextController.searchTermUpdate}" placeholder="Enter search term">
<p:ajax event="keyup" delay="300" update="markText" process="@this"/>
</p:inputText>
</h:panelGrid>
<p:panel id="searchContainer" header="Searchable Content" style="margin-top: 20px">
<p>#{markTextController.processedTextFirstPart}</p>
<p>#{markTextController.processedTextSecondPart}</p>
</p:panel>
<p:panel id="backendDataPanel" header="Backend Data from Mark Event Listener" style="margin-top: 20px">
<h:outputText value="Last Matched Terms: #{empty markTextController.lastMatchedTerms ? 'No data received yet' : markTextController.lastMatchedTerms}"/>
<br/>
<h:outputText value="Last Positions: #{empty markTextController.lastPositions ? 'No data received yet' : markTextController.lastPositions}"/>
</p:panel>
<pe:markText id="markText" for="searchContainer" value="#{markTextController.searchTermUpdate}"
styleClass="marktext-highlight" caseSensitive="false">
<p:ajax event="mark" listener="#{markTextController.onHighlight}" update="backendDataPanel messages"/>
</pe:markText>
</h:form>
</div>
<div class="card">
<h5>Done Callback</h5>
<p>
The client-side <code>onDone</code> callback fires after mark.js completes marking and receives the total number of matches,
without an AJAX request. Compare with <i>Update Search</i>, where a server-side <code>p:ajax event="mark"</code> listener
receives the matched terms and positions.
</p>
<h:form id="formDoneCallback">
<h:panelGrid columns="2" cellpadding="4">
<h:outputText value="Search term:"/>
<p:inputText id="doneSearchInput" value="#{markTextDoneController.searchTerm}" placeholder="Enter search term"
onkeyup="PF('markTextWvDone').updateMark(this.value)"/>
</h:panelGrid>
<p:panel id="searchContainer" header="Searchable Content" style="margin-top: 20px">
<p>#{markTextDoneController.processedText}</p>
</p:panel>
<p:panel id="donePanel" header="Done Callback Result" style="margin-top: 20px">
<h:outputText id="doneLabel" value="No search performed yet."/>
<br/>
<h:outputText value="Total matches: "/>
<h:outputText id="doneCount" value="0"/>
</p:panel>
<pe:markText id="markText" widgetVar="markTextWvDone" for="searchContainer" value="#{markTextDoneController.searchTerm}"
styleClass="marktext-highlight" caseSensitive="false"
onDone="markDone(totalMatches)"/>
</h:form>
<script>
function markDone(totalMatches) {
document.getElementById('formDoneCallback:doneCount').textContent = totalMatches;
document.getElementById('formDoneCallback:doneLabel').textContent = totalMatches === 0
? 'No matches found.'
: 'Marking complete: ' + totalMatches + ' match' + (totalMatches !== 1 ? 'es' : '') + ' highlighted.';
}
</script>
</div>
package org.primefaces.extensions.showcase.controller.marktext;
import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import jakarta.faces.application.FacesMessage;
import jakarta.faces.context.FacesContext;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Named;
import org.primefaces.extensions.event.MarkEvent;
import org.primefaces.extensions.model.marktext.MarkPosition;
/**
* MarkText Controller.
*
* @author jxmai
*/
@Named
@ViewScoped
public class MarkTextController implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String searchTerm = "ipsum";
private String searchTerm2 = "dolor";
private String customStyleClass = "custom-highlight";
private String searchTermCaseSensitivity = "Ut";
private String searchTermSeparateWord = "search is there";
private String searchTermAccuracy = "am";
private String searchTermUpdate = "lorem";
private String replaceTerm = "ipsum";
private String processedText = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
PrimeFaces Extensions provides additional components for PrimeFaces.\
MarkText is a new component that highlights search terms within specified containers using mark.js.""";
private List<String> lastMatchedTerms = new ArrayList<>();
private List<MarkPosition> lastPositions = new ArrayList<>();
private String lastMatchedTermsJson;
private String lastPositionsJson;
public String getSearchTerm() {
return searchTerm;
}
public void setSearchTerm(String searchTerm) {
this.searchTerm = searchTerm;
}
public String getSearchTerm2() {
return searchTerm2;
}
public void setSearchTerm2(String searchTerm2) {
this.searchTerm2 = searchTerm2;
}
public String getCustomStyleClass() {
return customStyleClass;
}
public void setCustomStyleClass(String customStyleClass) {
this.customStyleClass = customStyleClass;
}
public String getSearchTermCaseSensitivity() {
return searchTermCaseSensitivity;
}
public void setSearchTermCaseSensitivity(String searchTermCaseSensitivity) {
this.searchTermCaseSensitivity = searchTermCaseSensitivity;
}
public String getSearchTermSeparateWord() {
return searchTermSeparateWord;
}
public void setSearchTermSeparateWord(String searchTermSeparateWord) {
this.searchTermSeparateWord = searchTermSeparateWord;
}
public String getSearchTermAccuracy() {
return searchTermAccuracy;
}
public void setSearchTermAccuracy(String searchTermAccuracy) {
this.searchTermAccuracy = searchTermAccuracy;
}
public String getSearchTermUpdate() {
return searchTermUpdate;
}
public void setSearchTermUpdate(String searchTermUpdate) {
this.searchTermUpdate = searchTermUpdate;
}
public String getReplaceTerm() {
return replaceTerm;
}
public void setReplaceTerm(String replaceTerm) {
this.replaceTerm = replaceTerm;
}
public String getProcessedText() {
return processedText;
}
public void setProcessedText(String processedText) {
this.processedText = processedText;
}
public List<String> getLastMatchedTerms() {
return lastMatchedTerms;
}
public List<MarkPosition> getLastPositions() {
return lastPositions;
}
public void onHighlight(MarkEvent event) {
this.lastMatchedTerms = event.getMatchedTerms();
this.lastPositions = event.getPositions();
this.lastMatchedTermsJson = lastMatchedTerms.toString();
this.lastPositionsJson = lastPositions.stream()
.map(MarkPosition::toString)
.collect(Collectors.joining(", "));
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_INFO, "Mark Event Triggered",
"Found " + lastMatchedTerms.size() + " matched terms and " + lastPositions.size() + " positions."));
}
public String getProcessedTextFirstPart() {
return processedText.split("\n\n")[0];
}
public String getProcessedTextSecondPart() {
return processedText.split("\n\n")[1];
}
public String getLastMatchedTermsJson() {
return lastMatchedTermsJson;
}
public void setLastMatchedTermsJson(String lastMatchedTermsJson) {
this.lastMatchedTermsJson = lastMatchedTermsJson;
}
public String getLastPositionsJson() {
return lastPositionsJson;
}
public void setLastPositionsJson(String lastPositionsJson) {
this.lastPositionsJson = lastPositionsJson;
}
}
package org.primefaces.extensions.showcase.controller.marktext;
import java.io.Serial;
import java.io.Serializable;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Named;
/**
* MarkText Done Callback Controller.
*
* @author jxmai
* @since 16.0.0
*/
@Named
@ViewScoped
public class MarkTextDoneController implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String searchTerm = "lorem";
private String processedText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
+ " Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
+ " Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."
+ " Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
public String getSearchTerm() {
return searchTerm;
}
public void setSearchTerm(String searchTerm) {
this.searchTerm = searchTerm;
}
public String getProcessedText() {
return processedText;
}
public void setProcessedText(String processedText) {
this.processedText = processedText;
}
}