How to create an Autocomplete directive for jQueryUI AngularJS

I am trying to create a custom directive that uses the jQueryUI autocomplete widget. I want this to be as declarative as possible. This is the desired markup:

<div>
    <autocomplete ng-model="employeeId" url="/api/EmployeeFinder" label="{{firstName}} {{surname}}" value="id" />
</div>

So, in the above example, I want the directive to make an AJAX call with the specified url, and when the data is returned, show the value computed from the expression (s) from the result in the text box and set id for employeeId. This is my directive attempt.

app.directive('autocomplete', function ($http) {
    return {
        restrict: 'E',
        replace: true,
        template: '<input type="text" />',
        require: 'ngModel',

        link: function (scope, elem, attrs, ctrl) {
            elem.autocomplete({
                source: function (request, response) {
                    $http({
                    url: attrs.url,
                    method: 'GET',
                    params: { term: request.term }
                })
                .then(function (data) {
                    response($.map(data, function (item) {
                        var result = {};

                        result.label = item[attrs.label];
                        result.value = item[attrs.value];

                        return result;
                    }))
                });
                },

                select: function (event, ui) {                    
                    ctrl.$setViewValue(elem.val(ui.item.label));                    

                    return false;
                }
            });
        }
    }    
});

So, I have two problems - how to evaluate expressions in the label attribute and how to set the property from the value attribute in ngModel in my scope.

+4
source share
2 answers

(function () {
'use strict';

angular
    .module('app')
    .directive('myAutocomplete', myAutocomplete);

myAutocomplete.$inject = ['$http', '$interpolate', '$parse'];
function myAutocomplete($http, $interpolate, $parse) {

    // Usage:

    //  For a simple array of items
    //  <input type="text" class="form-control" my-autocomplete url="/some/url" ng-model="criteria.employeeNumber"  />

    //  For a simple array of items, with option to allow custom entries
    //  <input type="text" class="form-control" my-autocomplete url="/some/url" allow-custom-entry="true" ng-model="criteria.employeeNumber"  />

    //  For an array of objects, the label attribute accepts an expression.  NgModel is set to the selected object.
    //  <input type="text" class="form-control" my-autocomplete url="/some/url" label="{{lastName}}, {{firstName}} ({{username}})" ng-model="criteria.employeeNumber"  />

    //  Setting the value attribute will set the value of NgModel to be the property of the selected object.
    //  <input type="text" class="form-control" my-autocomplete url="/some/url" label="{{lastName}}, {{firstName}} ({{username}})" value="id" ng-model="criteria.employeeNumber"  />

    var directive = {            
        restrict: 'A',
        require: 'ngModel',
        compile: compile
    };

    return directive;

    function compile(elem, attrs) {
        var modelAccessor = $parse(attrs.ngModel),
            labelExpression = attrs.label;

        return function (scope, element, attrs) {
            var
                mappedItems = null,
                allowCustomEntry = attrs.allowCustomEntry || false;

            element.autocomplete({
                source: function (request, response) {
                    $http({
                        url: attrs.url,
                        method: 'GET',
                        params: { term: request.term }
                    })
                    .success(function (data) {
                        mappedItems = $.map(data, function (item) {
                            var result = {};

                            if (typeof item === 'string') {
                                result.label = item;
                                result.value = item;

                                return result;
                            }

                            result.label = $interpolate(labelExpression)(item);

                            if (attrs.value) {
                                result.value = item[attrs.value];
                            }
                            else {
                                result.value = item;
                            }

                            return result;
                        });

                        return response(mappedItems);
                    });
                },

                select: function (event, ui) {
                    scope.$apply(function (scope) {
                        modelAccessor.assign(scope, ui.item.value);
                    });

                    if (attrs.onSelect) {
                        scope.$apply(attrs.onSelect);
                    }

                    element.val(ui.item.label);

                    event.preventDefault();
                },

                change: function () {
                    var
                        currentValue = element.val(),
                        matchingItem = null;

                    if (allowCustomEntry) {
                        return;
                    }

                    if (mappedItems) {
                        for (var i = 0; i < mappedItems.length; i++) {
                            if (mappedItems[i].label === currentValue) {
                                matchingItem = mappedItems[i].label;
                                break;
                            }
                        }
                    }

                    if (!matchingItem) {
                        scope.$apply(function (scope) {
                            modelAccessor.assign(scope, null);
                        });
                    }
                }
            });
        };
    }
}
})();
+4

, ... , ng-repeat...

, Angular:)

EDIT: . elem.autocomplete , elem . IT . , ng-repeat. :

app.directive('autocomplete', function ($http, $interpolate, $parse) {
return {
    restrict: 'E',
    replace: true,
    template: '<input type="text" />',
    require: 'ngModel',

    compile: function (elem, attrs) {
        var modelAccessor = $parse(attrs.ngModel),
            labelExpression = attrs.label;

        return function (scope, element, attrs, controller) {
            var
                mappedItems = null,
                allowCustomEntry = attrs.allowCustomEntry || false;

            element.autocomplete({
                source: function (request, response) {
                    $http({
                        url: attrs.url,
                        method: 'GET',
                        params: { term: request.term }
                    })
                    .success(function (data) {
                        mappedItems = $.map(data, function (item) {
                            var result = {};                                    

                            if (typeof item === "string") {
                                result.label = item;
                                result.value = item;

                                return result;
                            }

                            result.label = $interpolate(labelExpression)(item);

                            if (attrs.value) {
                                result.value = item[attrs.value];
                            }
                            else {
                                result.value = item;
                            }

                            return result;
                        });

                        return response(mappedItems);
                    });
                },

                select: function (event, ui) {
                    scope.$apply(function (scope) {
                        modelAccessor.assign(scope, ui.item.value);
                    });

                    elem.val(ui.item.label);

                    event.preventDefault();
                },

                change: function (event, ui) {
                    var
                        currentValue = elem.val(),
                        matchingItem = null;

                    if (allowCustomEntry) {
                        return;
                    }

                    for (var i = 0; i < mappedItems.length; i++) {
                        if (mappedItems[i].label === currentValue) {
                            matchingItem = mappedItems[i].label;
                            break;
                        }
                    }                        

                    if (!matchingItem) {
                        scope.$apply(function (scope) {
                            modelAccessor.assign(scope, null);
                        });
                    }
                }
            });
        }
    }
}
});
+2

Source: https://habr.com/ru/post/1544478/


All Articles