0

I have a custom AngularJS directive for a textdown control. In it is an ng-repeat that is printing a list of div's for the emulated dropdown, and each item has an ng-click function. That function will not fire when the div is clicked. Can you please help me figure out why?

Plunkr: https://plnkr.co/edit/vOwtjqltq2WfCM9A0dFJ?p=preview

I don't remember where I first heard that concept, but it's very similar to StackOverflow's question Tags input, except you can only select 1 item. If you haven't seen that example, it is a text input that has a dropdown list when you start typing into it with related items that has fields that partially match what you've typed so far. Then the user can click on an item in the dropdown and it fills in the text input.

Here is the main page's HTML:

<!DOCTYPE html>
<html ng-app="plunker">

<head>
  <meta charset="utf-8" />
  <title>AngularJS Plunker</title>
  <script>
    document.write('<base href="' + document.location + '" />');
  </script>
  <link rel="stylesheet" href="style.css" />
  <script data-require="angular.js@1.4.x" src="https://code.angularjs.org/1.4.7/angular.js" data-semver="1.4.7"></script>
  <script src="app.js"></script>
  <script src="textdown.js"></script>
</head>

<body ng-controller="MainCtrl">
  <p>Hello and welcome to the Textdown example!</p>
  <label>City:
    <textdown input-placeholder-text="Select a City..." is-editable="true" items="cities" ng-model="selectedCity" title="Name" width="150px"></textdown>
  </label>
</body>

</html>

Here is the directive's HTML:

var HYG_TEXTBOX_DROPDOWN_TEMPLATE = '\
<div class="hyg-textdown-container activate-textdown" \
     ng-class="{ \'hyg-focused\': isFocused() }"> \
  <input type="search" class="activate-textdown" placeholder="{{ inputPlaceholderText }}" style="width: 100%;" \
         ng-class="{ \'invalid\': !isValid() }" \
         ng-change="onInputChanged()" \
         ng-focus="onInputFocus($event)" \
         ng-model="input" \
         ng-blur="onInputBlur()" \
         ng-show="isEditable"> \
  </input> \
  <div class="hyg-textdown-list activate-textdown" ng-show="selectActive" ng-style="{ top: ytop, left: xleft }" style="z-index:5; width: {{ width }}"> \
    <div class="hyg-textdown-listed activate-textdown" \
         ng-repeat="item in items | property: title: (ngModel != null ? \'\' : input) | orderBy: title | limitTo:5" \
         ng-class="{ \'hyg-textdown-listed-active\': isSelected(item) }" \
         ng-click="selectItem(item, $event);"> \
      <span class="activate-textdown">{{ item[title] }}</span> \
    </div> \
  </div> \
</div>';

Here is the module, directive, controller, and associated filter code:

angular.module("hyg.Textdown", [])
.directive("textdown", ["$compile", "$document", "$filter", "$log", "$timeout", function ($compile, $document, $filter, $log, $timeout) {
  return {
    restrict: "E",
    replace: false,
    controller: "hygTextdownCtrl",
    template: function (element, attrs) {
      return HYG_TEXTBOX_DROPDOWN_TEMPLATE;
    },
    require: "?ngModel",
    scope: {
      inputPlaceholderText: "@",
      isEditable: "=",
      items: "=",
      ngModel: "=",
      title: "@",
      width: "@"
    },
    link: function (scope, element, attrs) {
      scope.orderBy = $filter("orderBy");

      if (scope.isEditable == null)
        scope.isEditable = true;

      $document.bind("click", function (e) {
        var shouldHideSelectList = !Enumerable.From(e.target.classList).Any(function (x) { return x == "activate-textdown"; });

        if (shouldHideSelectList) {
          $timeout(function () { scope.selectActive = false; }, 0);
        }
      });

      scope.destroy = function () {
        if (scope.handler != null)
          scope.handler();
      };

      scope.isFocused = function () {
        return scope.focus;
      };

      scope.isSelectActive = function () {
        return scope.selectActive;
      };

      scope.isValid = function () {
        return scope.input == null || scope.input.length == 0 || scope.ngModel != null;
      };

      scope.onInputChanged = function () {
        var input = scope.input == null ? null : scope.input.toLowerCase();
        var item = Enumerable.From(scope.items).Where(function (x) { return x[scope.title].toLowerCase() == input; }).ToArray()[0];

        scope.selectItem(item);
      };

      scope.onInputFocus = function ($event) {
        scope.focus = true;
        scope.selectActive = true;
      };

      scope.onInputBlur = function () {
        scope.focus = false;
        scope.selectActive = false;
      };

      scope.selectItem = function (item, $event) {
        if (scope.isEditable) {
          scope.ngModel = item;

          if (item != null)
            scope.selectActive = false;
        }
      };

      scope.isSelected = function (item) {
        return scope.ngModel == item;
      };

      scope.handler = scope.$watch("ngModel", function () {
        if(scope.ngModel != null)
          scope.input = scope.ngModel[scope.title];
      });
    }
  }
}])
.controller("hygTextdownCtrl", ["$scope", function ($scope) {
  $scope.focus = false;
  $scope.handler = null;
  $scope.selectActive = false;
}])
.filter("property", ["$filter", function ($filter) {
  return function (array, propertyString, target) {
    if (target == null)
      return array;

    var matched = [];
    var toMatch = target.toLowerCase();

    angular.forEach(array, function (item) {
      if (item[propertyString].includes != undefined) {
        if (item[propertyString].toLowerCase().includes(toMatch)) {
          matched.push(item);
        }
      }
      else
      {
        if (item[propertyString].toLowerCase().indexOf(toMatch) > -1) {
          matched.push(item);
        }
      }
    });

    return matched;
  }
}]);

Thanks, Jibba

  • at line 47 'Enumerable' is undefined. (unless youve included linq.js in your project but not in plunkr) – David H. Jul 11 '16 at 21:26
  • remove ng-blur event from the template.It will work – Jameel Moideen Jul 11 '16 at 21:31
  • David, I am using linq.js in my project, and didn't properly set it up in the Plunkr. Thanks for catching that. – JibbaJamba Jul 27 '16 at 22:28
  • JAMEEL, the ng-blur is required to hide the textdown when the user starts interacting with any other element. Do you have another way to handle that but allow removing ng-blur? – JibbaJamba Jul 27 '16 at 22:29

1 Answers1

2

The reason why the ng-click is not firing is because before the option is clicked, blur event is fired on the input, which hides the options and your option never gets clicked.

You can try selecting option using ng-mousedown instead of ng-click.

Prashant
  • 7,340
  • 2
  • 25
  • 24
  • That worked, yay!!! It's confusing, though, because I'm doing almost the EXACT same thing in another directive (that allows selecting multiple items at the same time), and it works with ng-click. Blast! – JibbaJamba Jul 12 '16 at 16:52
  • Share the plunkr for that working directive, i'll have a look – Prashant Jul 12 '16 at 17:13
  • I actually changed the related directive to use ng-mousedown, too. Thanks for your help regardless. – JibbaJamba Jul 14 '16 at 16:49