0

When I click anywhere in the page apart from ul element (where countries are listed) and the suggestion-text input element (where I type country name), vm.suggested in controller should be set to null. As a result ul element will be closed automatically. How can I do this?

I've seen Click everywhere but here event and AngularJS dropdown directive hide when clicking outside where custom directive is discussed but I couldn't work out how to adapt it to my example.

enter image description here

Markup

<div>
    <div id="suggestion-cover">
        <input id="suggestion-text" type="text" ng-model="vm.countryName" ng-change="vm.countryNameChanged()">
        <ul id="suggest" ng-if="vm.suggested">
            <li ng-repeat="country in vm.suggested" ng-click="vm.select(country)">{{ country }}</li>
        </ul>
    </div>

    <table class="table table-hover">
        <tr>
            <th>Teams</th>
        </tr>
        <tr ng-if="vm.teams">
            <td><div ng-repeat="team in vm.teams">{{ team }}</div></td>
        </tr>
    </table>

    <!-- There are many more elements here onwards -->
</div>

Controller

'use strict';

angular
    .module('myApp')
    .controller('readController', readController);

function readController() {
    var vm = this;

    vm.countryNameChanged = countryNameChanged;
    vm.select = select;

    vm.teams = {.....};
    vm.countryName = null;
    vm.suggested = null;

    function countryNameChanged() {
        // I have a logic here
    }

    function select(country) {
        // I have a logic here
    }
}
BentCoder
  • 12,257
  • 22
  • 93
  • 165

2 Answers2

0

This is just an example but you can simply put ng-click on body that will reset your list to undefined.

Here's example:

http://plnkr.co/edit/iSw4Fqqg4VoUCSJ00tX4?p=preview

You will need on li elements:

$event.stopPropagation();

so your html:

<li ng-repeat="country in vm.suggested" ng-click="vm.select(country); $event.stopPropagation()">{{ country }}</li>

and your body tag:

<body ng-app="myWebApp" ng-controller="Controller01 as vm"  ng-click="vm.suggested=undefined;">

UPDATE:

As I said it's only an example, you could potentially put it on body and then capture click there, and broadcast 'closeEvent' event throughout the app. You could then listen on your controller for that event - and close all. That would be one way to work around your problem, and I find it pretty decent solution.

Updated plunker showing communication between 2 controllers here: http://plnkr.co/edit/iSw4Fqqg4VoUCSJ00tX4?p=preview

LAST UPDATE: Ok, last try - create a directive or just a div doesn't really matter, and put it as an overlay when <li> elements are open, and on click close it down. Currently it's invisible - you can put some background color to visualize it.

Updated plunker: http://plnkr.co/edit/iSw4Fqqg4VoUCSJ00tX4?p=preview

And finally totally different approach

After some giving it some thought I actually saw that we're looking at problem from the totally wrong perspective so final and in my opinion best solution for this problem would be to use ng-blur and put small timeout on function just enough so click is taken in case someone chose country:

on controller:

this.close = function () {
            $timeout(()=>{
              this.suggested = undefined;
            }, 200);
}

on html:

<input id="suggestion-text" type="text" ng-model="vm.countryName" ng-change="vm.countryNameChanged()" ng-blur="vm.close()">

This way you won't have to do it jQuery way (your solution) which I was actually trying to avoid in all of my previous solutions. Here is plnker: http://plnkr.co/edit/w5ETNCYsTHySyMW46WvO?p=preview

pegla
  • 1,866
  • 4
  • 17
  • 20
  • The problem with placing `ng-click` on `` tag is, my `` tag is in index.html under project root which loads my actual CountryView.html with `` so it is not feasible to do that. Another option is, if I put `ng-click` on main `
    ` element in my CountryView.html, your solution works only in part of the page so this is not feasible as well. I need a better way of doing this.
    – BentCoder Oct 01 '17 at 19:38
  • I updated the answer with one possibility how you could maybe do it with body or some other high level element – pegla Oct 01 '17 at 19:55
  • The `` is a global tag so I don't want to add a `ng-click` property to it which is only relevant to a single view not all views. That just doesn't feel right. Your example doesn't work anyway. Click somewhere towards the bottom when you have countries are visible. – BentCoder Oct 01 '17 at 20:35
  • If you don't like solution that's fine, I wouldn't do it as well, agree that body is not a place for it if it's only on one view. It's working though, just on plnker that's the size of body - so that's why it's working only on top part - body ends just after that "Teams", you can check it in inspector. Anyway I provided last solution I have for you, if it's not good enough I hope you will find some better answer. – pegla Oct 01 '17 at 22:14
  • I'll test it tomorrow and let you know. Thanks for taking time to answer my question. Much appreciated. – BentCoder Oct 01 '17 at 22:25
  • @pegia I posted the solution. Thanks for the help though. – BentCoder Oct 06 '17 at 13:55
  • That was actually one thing I was trying to avoid setting on click events on elements jQuery way, but I provided you with better solution if you agree let me know. – pegla Oct 06 '17 at 16:28
0

I solved the issue by calling controller function from within the directive so when user clicks outside (anywhere in the page) of the element, controller function gets triggered by directive.

View

<ul ng-if="vm.suggested" close-suggestion="vm.closeSuggestion()">

Controller

function closeSuggestion() {
    vm.suggested = null;
}

Directive

angular.module('myApp').directive('closeSuggestion', [
    '$document',
    function (
        $document
    ) {
        return {
            restrict: 'A',
            scope: {
                closeSuggestion: '&'
            },
            link: function (scope, element, attributes) {
                $document.on('click', function (e) {
                    if (element !== e.target && !element[0].contains(e.target)) {
                        scope.$apply(function () {
                            scope.closeSuggestion();
                        });
                    }
                });
            }
        }
    }
]);
BentCoder
  • 12,257
  • 22
  • 93
  • 165