Angularjs Custom select2指令

时间:2015-04-15 07:40:02

标签: javascript jquery angularjs angularjs-directive jquery-select2

我为这个很棒的jquery插件jQuery-Select2创建了简单的自定义AngularJs指令,如下所示:

指令

app.directive("select2",function($timeout,$parse){
    return {
        restrict: 'AC',
        link: function(scope, element, attrs) {
            $timeout(function() {
                $(element).select2();
            },200); 
        }
    };
});

HTML模板中的用法:

<select class="form-control" select2 name="country"
data-ng-model="client.primary_address.country"
ng-options="c.name as c.name for c in client.countries">
     <option value="">Select Country</option>
</select>

它按预期工作,我的普通select元素被select2插件替换。

然而,有一个问题,有时它显示默认值,即Select Country,尽管在下拉列表中正确选择模型值。

现在,如果我将$timeout间隔从200增加到某个高值,请说1500,它可以正常工作,但会延迟指令的呈现。此外,我认为这不适合它,因为我的数据是通过ajax加载的。

我也尝试按如下方式更新指令,但是没有运气:

app.directive("select2",function($timeout,$parse){
    return {
        restrict: 'AC',
        require: 'ngModel',
        link: function(scope, element, attrs) {
            var modelAccessor = $parse(attrs.ngModel);
            $timeout(function() {
                $(element).select2();
            });
            scope.$watch(modelAccessor, function (val) {
                if(val) {
                    $(element).select2("val",val);
                }
            });
        }
    };
});

PS:我知道有类似的模块存在ui-select,但它需要<ui-select></ui-select>形式的一些不同的标记,我的应用程序已经完全开发,我只想替换普通的选择框与select2。

那么请您指导我如何解决此问题并确保该指令与最新行为保持同步?

4 个答案:

答案 0 :(得分:3)

我对select2并不熟悉(因此获取和设置控件中显示值的实际API可能不正确),但我建议将其作为替代方案:

app.directive("select2",function($timeout){
    return {
        restrict: 'AC',
        require: 'ngModel',
        link: function(scope, element, attrs, model) {

            $timeout(function() {
                element.select2();
            });

            model.$render = function() {
                element.select2("val",model.$viewValue);
            }
            element.on('change', function() {
                scope.$apply(function() {
                    model.$setViewValue(element.select2("val"));
                });
            })
        }
    };
});

第一个$ timeout是必要的,因为你正在使用ng-options,所以在下一个摘要周期之前,选项不会在DOM中。这样做的问题是,如果您的应用程序稍后更改了国家/地区模型,则不会向控件添加新选项。

答案 1 :(得分:3)

Angular不希望第三方插件修改模型数据。我的猜测是基于你使用$ timeout的事实,Angular更新选项或模型和select2插件之间存在竞争条件。我想出的解决方案是主要从Angular的手中进行更新,并从指令手动完成,这样无论谁修改,你都可以确保一切都匹配。这是我提出的指令:

app.directive("select2",function($timeout,$parse){
    return {
        restrict: 'AC',
        link: function(scope, element, attrs) {
            var options = [],
                el = $(element),
                angularTriggeredChange = false,
                selectOptions = attrs["selectOptions"].split(" in "),
                property = selectOptions[0],
                optionsObject = selectOptions[1];
            // watch for changes to the defining data model
            scope.$watch(optionsObject, function(n, o){
                var data = [];
                // format the options for select2 data interface
                for(var i in n) {
                    var obj = {id: i, text: n[i][property]};
                    data.push(obj);
                }
                el.select2({data: data});
                // keep local copy of given options
                options = n;
            }, true);
            // watch for changes to the selection data model
            scope.$watch(attrs["selectSelection"], function(n, o) {
                // select2 is indexed by the array position,
                // so we iterate to find the right index
                for(var i in options) {
                    if(options[i][property] === n) {
                        angularTriggeredChange = true;
                        el.val(i).trigger("change");
                    }
                }
            }, true);
            // Watch for changes to the select UI
            el.select2().on("change", function(e){
                // if the user triggered the change, let angular know
                if(!angularTriggeredChange) { 
                    scope.$eval(attrs["selectSelection"]+"='"+options[e.target.value][property]+"'");
                    scope.$digest();
                }
                // if angular triggered the change, then nothing to update
                angularTriggeredChange = false;
            });

        }
    };
});

我已添加到属性select-optionsselect-model。这些将用于使用select2的界面填充和更新数据。这是一个示例html:

<select id="sel" class="form-control" select2 name="country"
  select-selection="client.primary_address.country" 
  select-options="name in client.countries" >
     <option value="">Select Country</option>
</select>
<div>Selected: {{client.primary_address.country}}</div>

请注意,仍然可以对指令进行一些清理,并且对输入有任何假设,例如&#34; in&#34;在select-options属性中。它也没有强制执行属性,但如果它们不存在就会失败。

另请注意,我已使用Select2第4版,el.val(i).trigger("change")证明了这一点。如果使用旧版本,则可能需要还原一些内容。

以下是行动中的jsfiddle demo指令。

答案 2 :(得分:1)

它没有直接回答你的问题,但请接受它,因为有些人想要采用另一种方法,而不是坚持使用jQuery select2。

我已经为此目的建立了自己的,因为我不满足现有的那些并不完全遵循Angular原则,HTML-first。

它还处于早期阶段,但我认为所有功能都适用于所有现代浏览器。

https://github.com/allenhwkim/angular-autocomplete

这些是例子

答案 3 :(得分:0)

我试图重现你的问题,它似乎运作良好。这是我提出的小提琴:

http://jsfiddle.net/s24gLdgq/

您可能有不同的行为,具体取决于您使用的Angular和/或Select2的版本,您能指定吗?

此外,如果您想防止闪烁,请务必隐藏默认的<select>标记,以便在弹出select2元素之前不显示任何内容。

这也是在我的jsfiddle中用CSS

完成的
.form-control { width: 200px; opacity: 0 }