【问题标题】:Is it possible to update the model after keypress but before keyup?是否可以在按键之后但在按键之前更新模型?
【发布时间】:2014-01-21 03:45:08
【问题描述】:

我正在使用 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

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

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

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

【问题讨论】:

    标签: angularjs angularjs-ng-repeat


    【解决方案1】:

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

    首先,控制器。很简单。

    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

    最后也是最重要的,指令部分:

    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 ||字符代码
    3. value 和 focus 使光标到第一个位置,所以需要先设置焦点然后添加 value

    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 
    />
    

    【讨论】:

    【解决方案2】:

    解决了:)顺便说一句。整洁的组件!这是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 功能,以便在控制器中完全创建一首新歌曲。也不是新指令 focus-me - 如果当前歌曲是新创建的歌曲,则输入字段获得焦点。

    控制器:

    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();
        }
      };
    });
    

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

    【讨论】:

    • 我已经在 IE11 和 Chrome 32 中尝试过;它只适用于输入第一首歌曲 - 你能再看一遍吗?
    • @KnewB 这部分不起作用。铬经过测试。 IE11 - 我没有 IE
    • @KnewB 抱歉。这是发布此之前的后期优化。函数名必须是 createNewSong 而不是 newSong
    • @KnewB 已解决 - 请参阅我的上一条评论和新的 plunkr
    • @KnewB 我们必须等到 angular 完成一个摘要循环。在该循环之后,我们可以确定输入值在 newSongTitle 属性中可用。使用 $timout,我们正在等待一个循环,并且该值在 newSongTitle 属性中是安全可用的。
    猜你喜欢
    • 2017-03-16
    • 1970-01-01
    • 1970-01-01
    • 2012-06-23
    • 2021-10-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多