7

In my following angular application, I have multiple rows of myelement (angular directive wrapper over input tag). At a time I need to focus/select/highlight one of it, .selected class in the styles does that.

In following application, everything works fine except focus to the input tag, which needs to be bounded by the css class selected. I.E. whatever element has class selected the corresponding input tag should be focused . How can I acieve that ?

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title></title>
  <meta name="description" content="">
  <meta name="viewport" content="width=device-width">
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
  <style>
    .container {
      display: flex;
      flex-direction: column;
      width: 600px;
    }
    .notebook {
      display: flex;
      justify-content: center;
    }
    .cell {
      margin: 5px;
      padding: 5px;
    }
    .selected {
      border-style: solid;
      border-color: green;
      border-width: 1px;
      border-left-width: 5px;
    }
  </style>
</head>

<body ng-app="myApp">

<div ng-controller="ListController as listctrl" class="notebook">

  <div class="container">
    <myelement ng-repeat="i in listctrl.list"
        ng-click="listctrl.selected = $index"
        ng-class="{selected : listctrl.selected === $index}"
        class="cell"></myelement>
  </div>
</div>

<script type="text/javascript">
angular
  .module('myApp',[])
  .controller('ListController', function($scope) {
    var listctrl = this;
    listctrl.list = [];
    listctrl.selected = 0;

    listctrl.addCell = function() {
      var x = listctrl.list.length;
      listctrl.list.push(x);
      listctrl.selected = listctrl.list.length - 1;
    }

    listctrl.addCell();

    $scope.$on('add', function (event, message) {
      $scope.$apply(listctrl.addCell);
    });

    $scope.$on('keyUp', function(event) {
      $scope.$apply(function(){
        listctrl.selected = listctrl.selected - 1;
      });
    });

    $scope.$on('keyDown', function(event) {
      $scope.$apply(function(){
        listctrl.selected = listctrl.selected + 1;
      });
    });
  })
  .directive('myelement', function($rootScope){

    return {
      template: '<input style="width: 95%"></input>',
      restrict: 'E',
      link: function (scope, element, attrs) {

        var inputTag = element[0].children[0];
        inputTag.focus();

        element.on('keydown', function(event) {
          if (event.keyCode === 13 && event.shiftKey) {
            $rootScope.$broadcast('add');
          } else if (event.keyCode === 38) {
            $rootScope.$broadcast('keyUp');
          } else if (event.keyCode === 40) {
            $rootScope.$broadcast('keyDown');
          }
        });
      },
      controller: function ($scope) {

      }
    };
  })
</script>

</body>
</html>
3
  • Shouldn't ng-class="{selected : listctrl.selected === $index}" have a "?" in between selected and listctrl.selected since it's a ternary? Or am I reading it wrong. Commented Oct 9, 2017 at 5:04
  • 1
    @Dream_Cap it's not a ternary, just an object literal Commented Oct 9, 2017 at 5:04
  • Can you not use something like element.hasClass('selected') && inputTag.focus()? You may need to set your directive's priority to something greater than 0 so its post-link function runs after ngClass Commented Oct 9, 2017 at 5:08

4 Answers 4

4
+25

Consider the following example. It uses the now recommended component feature of AngularJS (since v1.5). The example is very simple so you can easily understand what is happening and how to apply it in your project.

JavaScript

class MainController {

    constructor() {
        this.focused = true;
    }

}

class MyElementController {

    constructor($element) {
        this.$element = $element;
    }

    $onChanges(changes) {
        if (changes.focused.currentValue === true) {
            this.$element[0].getElementsByTagName('input')[0].focus();
        }
    }

}

const myElementComponent = {
    bindings: {
        focused: '<'
    },
    controller: MyElementController,
    template: `<input type="text">`
};

angular
    .module('app', [])
    .controller('MainController', MainController)
    .component('myElement', myElementComponent);

HTML

<body ng-app="app" ng-controller="MainController as vm">
    <my-element focused="vm.focused"></my-element>
</body>
Sign up to request clarification or add additional context in comments.

Comments

4

 var elementComponent = {
bindings:{
     selected:'<'
  },
  controller:function($element){
     this.$onChanges = function(changes) {
       if(changes.selected.currentValue){
         $element[0].getElementsByClassName('textName')[0].focus()
       }
    }
  },
  template:'<input type="text" class="textName"  style="margin:4px">'
};

var controller = function(){
   this.list = [1];
   this.selected = 1
   this.add = function(){
     var length = this.list.length ;
      this.list.push(length + 1);
      this.selected = length + 1;
   }
};


angular.module('app', [])
       .component('element', elementComponent)
       .controller('appCtrl', controller);
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
</head>
<body ng-app="app" ng-controller="appCtrl as vm" >
<script src="https://rawgit.com/angular/bower-angular/master/angular.min.js"></script>
  <button ng-click="vm.add()">Add New Cell</button>
  <div ng-repeat="item in vm.list"  >
    <element selected="item == vm.selected"  ng-click="vm.selected = item"></element>
  </div>
  Selected Element : {{vm.selected}}
</body>
</html>

This might fill your requirement.

Comments

1

on every key up/done, check the class, and use focus(), blur() to change input states. in case of tab press, preventDefault()

angular
  .module('myApp',[])
  .controller('ListController', function($scope) {
    var listctrl = this;
    listctrl.list = ['1','2','3'];
    listctrl.selected = 0;

    listctrl.addCell = function() {
      var x = listctrl.list.length;
      listctrl.list.push(x);
      listctrl.selected = listctrl.list.length - 1;
    }

    listctrl.addCell();

    $scope.$on('add', function (event, message) {
      $scope.$apply(listctrl.addCell);
    });

    $scope.$on('keyUp', function(event) {
      $scope.$apply(function(){
        listctrl.selected = listctrl.selected - 1;
      });
    });

    $scope.$on('keyDown', function(event) {
      $scope.$apply(function(){
        listctrl.selected = listctrl.selected + 1;
      });
    });
  })
  .directive('myelement', function($rootScope){
    return {
      template: '<input style="width: 95%"></input>',
      restrict: 'E',
      scope: {},
      link: function (scope, element, attrs) {
        var inputTag = element[0].children[0];
        var updateFocues = function(element) {
          if(element[0].className.indexOf('selected') !== -1) {
            scope.$apply(function() {
              inputTag.focus()
            });
          } else {
            scope.$apply(function() {
              inputTag.blur()
            });
          }    
        }

        element.on('keydown', function(event) {
          if (event.keyCode === 13 && event.shiftKey) {
            $rootScope.$broadcast('add');
          } else if (event.keyCode === 38) {
            $rootScope.$broadcast('keyUp');
          } else if (event.keyCode === 40) {
            $rootScope.$broadcast('keyDown');
          }else if (event.keyCode === 9) {
            event.preventDefault();
          }
        });
        

        scope.$on('keyUp', function() {
          updateFocues(element)
        })
        scope.$on('keyDown', function() {
          updateFocues(element)
        })
      },
      controller: function ($scope) {

      }
    };
  })
 .container {
      display: flex;
      flex-direction: column;
      width: 600px;
    }
    .notebook {
      display: flex;
      justify-content: center;
    }
    .cell {
      margin: 5px;
      padding: 5px;
    }
    .selected {
      border-style: solid;
      border-color: green;
      border-width: 1px;
      border-left-width: 5px;
    }
<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title></title>
  <meta name="description" content="">
  <meta name="viewport" content="width=device-width">
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>

</head>

<body ng-app="myApp">

<div ng-controller="ListController as listctrl" class="notebook">

  <div class="container">
    <myelement ng-repeat="i in listctrl.list"
        ng-click="listctrl.selected = $index"
        ng-class="{selected : listctrl.selected === $index}"
        class="cell"></myelement>
  </div>
</div>
</body>
</html>

Comments

0

Suggest you use css instead (most likely it will fit your needs). Adding extra JS code to support simple behaviours is not a good practice.

:focus selector explained on W3C

E.g.

myelement input:focus {
  border-style: solid;
  border-color: green;
  border-width: 1px;
  border-left-width: 5px;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.