Devil May Code...

Vergil's Blog

上一篇博文提到,是由于jQuery.val()方法设置DOM元素的值的时候,并没有触发AngularJS的$digest循环。然后我写了一个directive来解决这个问题。

那是否可以有一个更加通用的方法,让AngularJS适应其他使用val()方法设置值的jQuery插件呢?如何让val()自动触发DOM事件??

jQuery Hooks

豆浆油条求助,得知jQuery有一个API,称为“hook(钩子)”(怪自己不去看官方文档)。

Hooks可以使用在val()css()attr()prop()这些getter/setter方法上。如果你设置了hook,当你调用这些函数的时候,jQuery会触发你hook定义的方法。

Hooks签名如下:

var someHook = {
    get: function(elem) {
        // obtain and return a value
        return "something";
    },
    set: function(elem, value) {
        // do something with value
    }
}

.val() Hooks

valHooks允许我们拦截.val()的功能

HTML:

<input type="text" id="t" />

JavaScript:

$.valHooks.text = {
    set: function(elem, val) {
        elem.value = 'hello  ' + val;
        return true;
    },
    get: function(elem) {
        return 'fuck';
    }
}

$('#t').val('world!');      //设置值为:hello world!
$('#t').val();              //返回:fuck

.css() Hooks

以后补充

.prop()和.attr() Hooks

以后补充

使用valHooks让jQuery.val()与AngularJS模型数据同步

知道Hooks之后,我们就可以拦截.val()的功能,在设置好值之后,触发一个让AngularJS进入$degist的情况。比如,触发input事件

Example:

<!doctype html>
<html ng-app="app">
    <head>
        <script src="http://cdn.bootcss.com/jquery/2.2.0/jquery.min.js"></script>
        <script src="http://cdn.bootcss.com/angular.js/1.4.8/angular.min.js"></script>
        <script>
            $(function(){
                $('#setvalue').on('click', function() {
                    $('#input').val('hello world!');
                });

                $.valHooks.text = {
                    set: function(elem, val) {
                        elem.value = val;   //把DOM元素设置为传进来的val值
                        $(elem).trigger('input');  //触发input事件
                        return true;
                    }
                };
            });
        </script>
    </head>
    <body>
        <div>
            <input type="text" ng-model="value" id="input" />
            <button type="button" id="setvalue">jQuery set value</button>
            <p>ng model value is:{{value}}</p>
        </div>
    </body>
</html>

OK,那么现在,使用val()方法设置元素的值之后,也可能让AngularJS的模型同步了。

一般情况下,这样子确实可以解决问题,但如果你不想让它触发input事件呢?比如说,你的input事件有另外的功能。

我的思路就是,触发一个自定义事件,写一个directive监听该事件。

Example:

<!doctype html>
<html ng-app="app">
    <head>
        <script src="http://cdn.bootcss.com/jquery/2.2.0/jquery.min.js"></script>
        <script src="http://cdn.bootcss.com/angular.js/1.4.8/angular.min.js"></script>
        <script>
            $(function(){
                $('#setvalue').on('click', function() {
                    $('#input').val('hello world!');
                });

                $.valHooks.text = {
                    set: function(elem, val) {
                        elem.value = val;
                        $(elem).trigger('jquery.set.value');    //触发自定义事件
                        return true;
                    }
                };
            });
        </script>
    </head>
    <body>
        <div>
            <!-- 注意这个input标签使用了jqSetValue组件 -->
            <input type="text" ng-model="value" id="input" jq-set-value />
            <button type="button" id="setvalue">jQuery set value</button>
            <p>ng model value is:{{value}}</p>
        </div>
        <script>
            angular.module('app', []).directive('jqSetValue', function() {
                return {
                    restrict: 'AC',
                    require: '?ngModel',
                    link: function(scope, element, attrs, ngModel) {
                         //设置自定义事件的回调函数
                        element.on('jquery.set.value', function(event) {
                            if(!ngModel) return;
                            scope.$apply(function(){
                                ngModel.$setViewValue($(element).val());
                            });
                        });
                    }
                };
            });
        </script>
    </body>
</html>

今天在AngularJS下使用Fuel UXspinbox时出现了问题:

如果手动输入input的值,一切正常。但如果使用spinbox的增加/减少按钮时,DOM已经被改变,$scope却无法同步修改后的值。

示例代码如下:

<div class="spinbox" data-initialize="spinbox" id="mySpinbox">
  <input type="text" class="form-control input-mini spinbox-input" ng-model="number">
  <div class="spinbox-buttons btn-group btn-group-vertical">
    <button type="button" class="btn btn-default spinbox-up btn-xs">
      <span class="glyphicon glyphicon-chevron-up"></span><span class="sr-only">Increase</span>
    </button>
    <button type="button" class="btn btn-default spinbox-down btn-xs">
      <span class="glyphicon glyphicon-chevron-down"></span><span class="sr-only">Decrease</span>
    </button>
  </div>
</div>

Why?

查看FuelUX源码可以发现它是使用jQuery的val()方法去修改DOM的值的(猜也猜到-_-||)。所以并没有启动$digest循环,详细看这篇文章

解决办法

最简单的方法就是直接改FuelUX的源码,找到它调用render()方法之后,触发一下input事件:

this.$element.trigger('input');     

不过这种修改别人源码的做法非常不好

那么,我写了一个directive来更新模型:

angular.module('your-app').directive('spinbox', ['$parse', function($parse) {
    return {
        restrict: 'C',      //以ClassName的形式被声明
        link: function(scope, element, attrs) {
            element.on('changed.fu.spinbox', function() {
                var $spinbox = angular.element(this);
                var $input = $spinbox.find('.spinbox-input');
                var model = $input.attr('ng-model');

                if (!model) return;

                var setter = $parse(model).assign;
                scope.$apply(function(){
                    setter(scope, $spinbox.spinbox('value'));
                });
            });

        }
    }
}]);

使用jQuery钩子与AngularJS模型数据同步

OK,如果有更好的方法,欢迎讨论:)


Powered by Typecho.