是否可以在按键之后但在键盘之前更新模型?

时间:2014-01-09 23:50:15

标签: angularjs angularjs-ng-repeat

我正在使用jQuery开发类似todo的应用程序,但我想切换到Angular。

有一个用于添加新项目的输入字段,但只要在此输入中输入任何内容,键击和其后的键击将有效地移动到现有项目组中的新项目。这意味着新项目输入文本框始终为空。更好地展示而不是解释:

http://jsfiddle.net/29Z3U/4/(删除了许多详细信息,但我所指的应用程序的方面已经过验证)。

<h1>Song List</h1>
<form id="songs">
    <ul id="sortable_songs"></ul>
</form>

<ul id="new_song">
    <script id="song_form_template" type="text/x-handlebars-template">
        <li class="song" id="{{song_id}}">
            <input type="text" placeholder="enter song" autofocus />                        
        </li>
    </script>
</ul>

一些jQuery:

var template = Handlebars.compile($('#song_form_template').html()),

    counter = (function(){var i=0; return function(){return ++i};})(),

    cloneNewSong = function(){
        var count = counter(),
            templateVals = {song_id: 'song_' + count};
        $('ul#new_song').append(template(templateVals));
    },

    addSong = function(event){
        //exclude certain keys here
        cloneNewSong();
        container = $(event.target).closest('li.song');
        container.appendTo('ul#sortable_songs');
        $(event.target)
            .removeAttr('placeholder')
            .focus(); //what I just typed disappears without this! why?
    };

$('ul#new_song').on('keypress', 'input', addSong);
cloneNewSong();

请注意,新项目输入文本框始终为空,焦点的行为应该如此,这样您就可以继续打印而不会中断。

应用程序代码变得越来越长,我甚至还没有尝试显示从JSON派生的现有歌曲列表。当然在Angular中,ngRepeat使这很容易。但是,我对Angular版本的尝试不起作用:http://plnkr.co/edit/xsGRiHFzfsVE7qRxgY8d?p=preview

<!DOCTYPE html>
<html ng-app="songListApp">
  <head>
    <script src="//code.angularjs.org/1.2.7/angular.js"></script>
    <link href="style.css" rel="stylesheet" />
    <script src="script.js"></script>
  </head>

  <body ng-controller="songListController">
    <h1>Song List</h1>

    <ul id="songs">
        <li ng-repeat="song in songs">
            <input type="text" ng-model="song.song_name" />
        </li>
    </ul>

    <form>
        <input 
          ng-keypress="newSong(new_song)" 
          ng-model="new_song.title" 
          placeholder="enter song" autofocus 
        />
    </form>

  </body>
</html>

JS:

var myapp = angular.module('songListApp', []);

myapp.controller('songListController', function($scope){
    songs = [
        {"song_name":"song 1","more_info":""},
        {"song_name":"song 2","more_info":""},
        {"song_name":"song 3","more_info":""}
    ];
    $scope.songs = songs;

    $scope.newSong = function(new_song){
        var song = {"song_name": new_song.title, "more_info":""};
        songs.push(song);
        new_song.title = '';
    };
});

在解决焦点管理问题之前,我注意到Angular模型的更新总是落后于一次击键。我认为这是因为按键事件发生before the character is inserted into the DOM

我意识到从按键切换到keyup会改变一些事情,但最初的设计是基于按键的响应性。

我尝试了一个自定义指令绑定到input event,但事情并没有像我希望的那样:http://plnkr.co/edit/yjdbG6HcS3ApMo1T290r?p=preview

我注意到angularjs.org的第一个代码示例(没有控制器的基本绑定)似乎没有我遇到的问题 - 在释放密钥之前更新了示例模型。

2 个答案:

答案 0 :(得分:6)

虽然有一个公认的答案,但我提出了一种不同的方法,这种方法更简单,控制器污染更少。

首先,控制器。它很简单。

myapp.controller('songListController', function($scope, $timeout){
    $scope.songs = [
        {"song_name":"song 1","more_info":""},
        {"song_name":"song 2","more_info":""},
        {"song_name":"song 3","more_info":""}
    ];
}); 

其次,标记部分

<input ng-keypress="createAndFocusIn(songs)" 
  create-and-focus-in="#songs input"
  placeholder="enter song" 
  autofocus 
/>

解释,

  1. 按下某个键时,它会创建一首歌并将焦点放在歌曲列表createAndFocusIn(songs)中。
  2. create-and-focus-in指令提供scope.createAndFocusIn(),因此除非使用此指令,否则控制器不必具有此功能。它接受选择器,在哪里创建新元素,在本例中为#songs input
  3. 最后但最重要的是,指令部分:

    myapp.directive('createAndFocusIn', function($timeout){
      return function(scope, element, attrs){
        scope.createAndFocusIn = function(collection) {
          collection.push({song_name: String.fromCharCode(event.keyCode)});
          $timeout(function() {
            element[0].value = '';
            var el = document.querySelectorAll(attrs.createAndFocusIn); 
            el[el.length-1].focus();
          });
        };
      };
    });
    

    在指令中,除了由属性指定外,它不会做很多事情。

    那就是它。

    这是有效的演示:http://plnkr.co/edit/mF15utNE9Kosw9FHwnB2?p=preview

    ----编辑---- @KnewB说它在FF中不起作用。这里有Chrome / FF工作版。

    在FF中,

      按下键时未设置
    1. window.event。我们需要传递参数
    2. event.keyCode不存在,我们需要同时检查keyCode ||则charCode
    3. 值和焦点使光标位于第一个位置,因此需要先设置焦点然后再添加值
    4. http://plnkr.co/edit/u2RtHWyhis9koQfhQEdW?p=preview

      myapp.directive('createAndFocusIn', function($timeout){
        return function(scope, element, attrs){
          scope.createAndFocusIn = function(ev, collection) {
            collection.push({});
            $timeout(function() {
              element[0].value = '';
              var el = document.querySelectorAll(attrs.createAndFocusIn);
              el[el.length-1].focus();
              el[el.length-1].value = String.fromCharCode(ev.keyCode||ev.charCode);
            });
          };
        };
      });
      

      $event现已通过:

      <input ng-keypress="createAndFocusIn($event, songs)" 
          create-and-focus-in="#songs input"
          placeholder="enter song" 
          autofocus 
      />
      

答案 1 :(得分:1)

解决了:)顺便说一句。整洁的组件!这是plunkr:http://plnkr.co/edit/wYsFRUcqTZFv5uIE0MWe?p=preview

html:

  <body ng-controller="songListController">
    <h1>Song List</h1>

    <ul id="songs">
        <li ng-repeat="song in songs">
            <input type="text" ng-model="song.song_name" focus-me="song === newSong"/>
        </li>
    </ul>

    <form>
        <input 
          ng-keypress="createNewSong()" 
          ng-model="newSongTitle" 
          placeholder="enter song" autofocus 
        />
    </form>

  </body>

我更改了ng-keypress功能,以便在控制器中完全创建新歌曲。也不是新指令专注于我 - 如果当前歌曲是新创建的歌曲,输入字段将获得焦点。

控制器:

myapp.controller('songListController', function($scope, $timeout){
    $scope.songs = [
        {"song_name":"song 1","more_info":""},
        {"song_name":"song 2","more_info":""},
        {"song_name":"song 3","more_info":""}
    ];

    $scope.newSongTitle = '';
    $scope.newSong = null;

    $scope.createNewSong = function(){
        $timeout(function(){
          $scope.newSong = {"song_name": $scope.newSongTitle, "more_info":""};
          $scope.songs.push($scope.newSong);
          $scope.newSongTitle = '';
        });
    };
});

正如您所看到的,新歌的创建由$ timeout调用包装。此调用延迟执行,直到下一个摘要周期发生,因此没有待处理的事件可以打断我们。

最后指令:

myapp.directive('focusMe', function(){
  return function($scope, element, attr){
    if($scope.$eval(attr.focusMe)){
        element[0].focus();
    }
  };
});

肯定是通用的,这样每个表达式都可以触发焦点。