1

我已经开始使用 Angular js,并且遇到了一个问题,需要在我的控制器中获取 DOM 的当前状态。基本上,我正在一个 contenteditable div 中构建一个文本编辑器。对 div 中文本的修订可以来自外部服务(来自服务器的长轮询推送)以及实际在字段中输入的用户。现在来自服务器的修订正在操纵我的角度模型,然后通过 ng-bind-html-unsafe 指令更新视图。唯一的问题是这会影响用户当前的光标位置和文本选择。

我已经找到了解决这个问题的方法,但它需要在我的控制器中直接操作 dom 元素,这在 Angular 中似乎是不鼓励的。我正在寻找对我当前方法的验证,或者对更“有角度”的东西的推荐。

基本上我所做的是在我的模型中添加了两个事件,“contentChanging”和“contentChanged”。第一个是在我更新模型之前触发的,第二个是在我更新模型之后触发的。在我的控制器中,我像这样订阅这些事件。

//dmp is google's diff_match_patch library
//rangy is a selection management library http://code.google.com/p/rangy/wiki/SelectionSaveRestoreModule
var selectionPatch;
var selection;
scope.model.on("contentChanging", function() {
    var currentText = $("#doc").html();       
    selection = rangy.saveSelection();
    var textWithSelection = $("#doc").html();
    selectionPatch = dmp.patch_make(currentText, textWithSelection);
});
scope.model.on("contentChanged", function() {
    scope.$apply();
    var textAfterEdit = $("#doc").html();
    $("#doc").html(dmp.patch_apply(selectionPatch, textAfterEdit)[0]);
    rangy.restoreSelection(selection);
});

所以基本上,当内容发生变化时,我会抓取可编辑区域的当前 html。然后我使用rangy插件将隐藏的 dom 元素注入到文档中来标记用户当前的位置和选择。我使用不带隐藏标记的 html 和带标记的 html,并使用 google 的 diff_match_patch 库(dmp)制作补丁。

更改内容后,我调用 scope.$apply() 来更新视图。然后我从视图中获取新文本并应用之前的补丁,这会将隐藏的标记添加回 html。最后我使用范围来恢复选择。

我不喜欢的部分是我如何使用 jquery 从视图中获取当前 html 以构建和应用我的补丁。这会让单元测试变得有点棘手,而且感觉不太对劲。但是考虑到 rangy 库的工作方式,我想不出另一种方法来做到这一点。

4

1 回答 1

2

这是一个简单的示例,说明您将如何开始:

<!doctype html>
<html ng-app="myApp">
<head>
    <script src="http://code.angularjs.org/1.1.2/angular.min.js"></script>
    <script type="text/javascript">
    function Ctrl($scope) {
        $scope.myText = "Here's some text";
    }

    angular.module("myApp", []).directive('texteditor', function() {
        return {
            restrict: 'E',
            replace: true,
            template: '<textarea></textarea>',
            scope: {
                text: '=' // link the directives scopes `text` property
                          // to the expression inside the text attribute
            },
            link: function($scope, elem, attrs) {
                elem.val($scope.text);
                elem.bind('input', function() {
                    // When the user inputs text, Angular won't know about
                    // it since we're not using ng-model so we need to call 
                    // $scope.$apply() to tell Angular run a digest cycle
                    $scope.$apply(function() {
                        $scope.text = elem.val();
                    });
                });
            }
        };
    });
    </script>
</head>
<body>
    <div ng-controller="Ctrl">
        <texteditor text="myText"></texteditor>
        <p>myText = {{myText}}</p>
    </div>
</body>
</html>

它只是绑定到一个文本区域,所以你可以用你真正的文本编辑器替换它。关键是在你的文本编辑器中监听文本的变化,并更新你的范围内的值,以便外界知道用户在文本编辑器中更改了文本。

于 2013-02-25T16:33:47.423 回答