Having trouble getting two different filters to work properly when filtering data in AngularJs

I have created a plunkr to demonstrate my current situation:

The user is required to type a word into the textbox, and upon clicking the button, an angular service retrieves data from a DB based on the input text. The retrieved data is then displayed in a table.

I am currently working on implementing a filter search. The search can be performed on a person's information such as:

  • first_name
  • middle_name
  • first_surname
  • second_surname

I have incorporated two visual filters:

1) Visual Filter for hiding and showing results: (defined in appCtrl.js)

$scope.changedValue=function(){
    var condition = $scope.filter.condition;
    $scope.Model.filteredlist = filterFilter($scope.Model.expenses,function(value, index, array){
      var fullname = (value.first_name+' '+value.middle_name+' '+value.first_surname+' '+value.second_surname).toLowerCase();
      if (fullname.indexOf(condition.replace(/\s\s+/g, ' ').toLowerCase()) > -1 ) {
        return array;
      }
    });
    if (typeof $scope.Model.filteredlist != 'undefined') { // When page loads for first time
      $scope.setPage();
    }
  } 

2) Visual Filter for highlighting the results: (defined in appDrct.js)

app.directive('highLight', function ($document, $sce) {
  var component = function(scope, element, attrs) {

    if (!attrs.highlightClass) {
      attrs.highlightClass = 'angular-highlight';
    }

    var replacer = function(match, item) {
      return '<span class="'+attrs.highlightClass+'">'+match+'</span>';
    }

    var tokenize = function(keywords) {
      keywords = keywords.replace(new RegExp(',$','g'), '').split(' ');
      var i;
      var l = keywords.length;
      for (i=0;i<l;i++) {
        keywords[i] = keywords[i].replace(new RegExp('^ | $','g'), '');
      }
      return keywords;
    }

    scope.$watch('keywords', function(newValue, oldValue) {
      console.log("new: " + newValue + " old " + oldValue);

        var tokenized = tokenize(newValue);
        var regex     = new RegExp(tokenized.join('|'), 'gmi');

        if(newValue.length>=1 || oldValue.length>=1){
          for(i=0;i<=1;i++){
            element[0].cells[i].innerHTML = element[0].cells[i].innerText.replace(regex, replacer);
          }
        }
    });
  }
  return {
    link:       component,
    replace:    false,
    scope:      {
      keywords:  '=highLight'
    }
  };
});

The html calling those filters: (defined in table.html)

<input type="text" class="form-control" id="filter-list" placeholder="Name(s) and/or Lastname(s)" ng-model="filter.condition" ng-change="changedValue()">
......
<tr ng-repeat="expense in Model.filteredlist | pagination: pagination.currentPage : numPerPage" x-high:light="filter.condition">
        <td>{{expense.first_name}} {{expense.middle_name}}</td>
        <td>{{expense.first_surname}} {{expense.second_surname}}</td>
        <td>{{expense.age}}</td>
      </tr>

However, I encountered some issues where certain data is not rendered correctly when there are missing values for middle_name or second_surname.

To replicate the issue, enter "Lora" in the search box, then delete it. You will notice that some data is not displayed correctly. Also, if you type "Loras" and then erase the "s", the word does not highlight again until you continue erasing characters.

I suspect the issue lies within the $scope.changeValue filter, but I'm unsure of how to resolve it.

Any suggestions or ideas?

Answer №1

The issue lies within the implementation of your highLight directive, as it is attempting to modify its contents without full knowledge of what those contents are...


element[0].cells[i].innerHTML = element[0].cells[i].innerText.replace(regex, replacer);

Ultimately, the problem stems from timing issues. At times, the highLight directive alters the HTML prior to interpolation occurring, resulting in instances such as:


<td class="ng-binding">{{expense.first_name}} {{expense.midd<span class="angular-highlight">l</span>e_name}}</td>

which Angular cannot interpret correctly.

Answer №2

There appears to be a known issue with Angular that can be found at this link - https://github.com/angular/angular.js/issues/11716

If you replace your {{ }} bindings with ng-bind, the filtering will function as intended -

    <td><span ng-bind="expense.first_name"></span><span ng-bind="expense.middle_name"></span></td>
    <td><span ng-bind="expense.first_surname"></span><span ng-bind="expense.second_surname"></span></td>
    <td><span ng-bind="expense.age"></span></td>

----- UPDATE - Jan,4, 2016 -----

I have not yet found a definitive explanation for this issue. It seems to be linked to how ng-bind is utilized to $watch things compared to how {{ }} is used to $observe. I'm still uncertain about it.

According to Angular best practices - https://github.com/angular/angular.js/blob/2a156c2d7ec825ff184480de9aac4b0d7fbd5275/src/ng/directive/ngBind.js#L16, ng-bind is the preferred method for binding values in scope unless they are DOM attributes, in which case using $observer in the directive could be appropriate. Reference - AngularJS : Difference between the $observe and $watch methods

Another distinction - the {{ }} watcher triggers on every $digest unlike ng-bind, which actively $watch for changes resulting in better performance, despite requiring more html code. Reference - AngularJS : Why ng-bind is better than {{}} in angular?

----- UPDATE - Jan,5, 2016 -----

For the correct answer, please see Pete BD's response below.

Answer №3

After receiving insightful responses from FrailWords and PeteBD, a new idea struck me, and it turned out to be successful!

The key lies in the concept of interpolation. By referencing the documentation and studying a helpful example on fiddle, I implemented the solution utilizing $interpolate and $eval within a non isolated scope.

var interpolation = $interpolate(element[0].cells[i].innerText);
element[0].cells[i].innerHTML = scope.$eval(interpolation).replace(regex, replacer);

Here is the complete code snippet for the directive:

app.directive('highLight', ['$interpolate', function ($interpolate) {
  var component = function(scope, element, attrs) {

    if (!attrs.highlightClass) {
      attrs.highlightClass = 'angular-highlight';
    }

    var replacer = function(match, item) {
      return '<span class="'+attrs.highlightClass+'">'+match+'</span>';
    }

    var tokenize = function(keywords) {
      keywords = keywords.replace(new RegExp(',$','g'), '').split(' ');
      var i;
      var l = keywords.length;
      for (i=0;i<l;i++) {
        keywords[i] = keywords[i].replace(new RegExp('^ | $','g'), '');
      }
      return keywords;
    }

    scope.$watch(attrs.highLight, function(newValue, oldValue) {
      console.log("new: " + newValue + " old " + oldValue);

        var tokenized = tokenize(newValue);
        var regex     = new RegExp(tokenized.join('|'), 'gmi');

        if(newValue.length>=1 || oldValue.length>=1){
          for(i=0;i<=1;i++){
            var interpolation = $interpolate(element[0].cells[i].innerText);
            element[0].cells[i].innerHTML = scope.$eval(interpolation).replace(regex, replacer);
          }
        }
    });
  }
  return {
    link:       component,
    replace:    false
  };
}]);

This is how the HTML structure should look:

<tr ng-repeat="expense in Model.filteredlist | pagination: pagination.currentPage : numPerPage" x-high:light="filter.condition">
 <td>{{expense.first_name}} {{expense.middle_name}}</td>
 <td>{{expense.first_surname}} {{expense.second_surname}}</td>
 <td>{{expense.age}}</td>
</tr>

Now, everything functions seamlessly. It's truly remarkable and undeniably effective.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Table Header Stays Put Without Shrinking or Expanding with Window Adjustment

I have a sticky table header that stays at the top when scrolling down on my web page. To achieve this effect, I followed the solution provided on css-tricks.com/persistent-headers/. However, I encountered an issue where the sticky table header does not ...

Choosing the image that represents your website in Safari web previews

Every time I check iCloud.com on my Safari top sites, the thumbnail is always the same. I'm curious about how I can automate this for my own website. ...

What is causing the recurring failure of file input changes to take effect?

Here is the code snippet I'm working with: <input type="file" #fileInput ng2FileSelect [uploader]="uploader" (onFileSelected)="onFileSelected($event)" /> Along with the handler function: public onFileSelected(e: Fi ...

Problem integrating multer with Express JS and Node JS

Click here to access the server.js file on GitHub Following a tutorial, I have implemented code accordingly. However, when working with an updated version of Express JS, errors are being thrown on the side of Express JS. Error C:\nodefiles&bso ...

What is the best way to create an arrow connecting a parent div to multiple child divs?

I'm faced with a challenge involving a reusable React list component that supports nested children. My goal is to visually connect the parent div to its direct children using arrows, similar to the image linked below. View List Component Image Below ...

Execute the function prior to executing the controller

I have created a factory named auth like this: app.factory('auth', [function () { var auth = {}; auth.getUser = function () { ... ... // retrieve user information from various sources } return auth }]); For man ...

Learn how to move a div inside another div using drag and drop, ensuring that the entire element moves along with it

I am trying to implement jQueryUI for my project. I have a list of components that I want to drag and drop into a grid system, similar to a designer tool. I am using the following code to make the div draggable: $( "#Component1" ).draggable(); Then I dro ...

Using various jQuery autocomplete features on a single webpage

UPDATE I have observed that the dropdown elements following the initial one are not being populated correctly. .data( 'ui-autocomplete' )._renderItem = function( ul, item ) { return $( "<li></li>" ) .data( "i ...

Why is the Zip archive downloader not functioning properly when using Node.js and Archiver (Unexpected end of archive error)?

Looking to download multiple files using archiver with express. The server should respond to a post request from the client by sending a .zip file. However, there seems to be an issue where WinRAR displays an error message "! 98I9ZOCR.zip:Unexpected end of ...

Storing User IP Address in Database with Express and Mongoose

I'm looking for a way to store users' IP addresses in mongoDB by using a mongoose model file for the user. Does anyone have any suggestions on how I can achieve this? Here is an example of the schema for the Users module file: const userSchema ...

Scan for every header tag present and verify the existence of an id attribute within each tag. If the id attribute is absent, insert

Looking to locate all header tags within the content and verify if each tag has an id attribute. If not, then jQuery should be used to add the id attribute. Here is the code snippet: var headings = $("#edited_content").find("h1,h2,h3,h4,h5,h6"); $.each( ...

What steps should I take to ensure that this modal remains persistent?

I am searching for a method to keep this modal persistent even after it appears. Currently, the user can easily close it by clicking outside of the div. <!DOCTYPE html> <html lang="en"> <head> <title>Bootstrap Example</title ...

Building an expansive navigation menu with react-bootstrap

My current project involves creating a mega menu. Although I successfully made a responsive navbar, the challenge now is to implement a dropdown panel with 100% width. I've tried various approaches but haven't found one that works. Note: The oth ...

Achieving cell merging in react-table can be accomplished by utilizing the appropriate

Currently, I am using react-table and have the need to combine cells in specific columns based on their content. Essentially, I want to remove the divider border between these merged cells. To give you a visual idea, this is how it currently looks like: ...

Is there a method to track the progress of webpage loading?

I am working on a website built with static HTML pages. My goal is to implement a full-screen loading status complete with a progress bar that indicates the page's load progress, including all images and external assets. Once the page has fully loaded ...

Transferring Data between Rails and Angularjs using JSON

Utilizing Angularjs to fetch JSON data from a Rails test app deployed on Heroku is proving to be a bit challenging. Below you can find the snippets of my Angular and Rails code. An error message displayed in my Firebug console reads: "NetworkError: 404 N ...

The function cannot be invoked. The 'Boolean' type does not have any call signatures. An error has occurred in the computed property of Vue3

Currently, I am working on creating a computed property that checks if an item is in the array. The function I have created returns a boolean value and takes one parameter, which is the item to be checked. isSelected: function (item: MediaGalleryItemTypes) ...

Continuous Scrolling with Callback Function in jQuery

I have implemented the infinite-scroll plugin, which replaces traditional pagination with ajax to fetch new pages. The issue I am facing is that jQuery functions do not recognize the new posts, causing certain functions like the ones below to stop working ...

Troubleshooting an Angular.js "Hello World" application that is malfunctioning

I've been trying to get a simple Hello World app working by following different tutorials, but it doesn't seem to be functioning correctly. I've double-checked my code and everything looks right to me. Could someone please assist me in troub ...

I keep encountering an issue with getJson

A snippet of my JavaScript code retrieves a JSON object from a URL. Here is the portion of the code in question: <script> $(document).ready(function(){ $("button").click(function(){ $.getJSON('url_address', {}, function(data) { ...