AngularJS адаптация ui-select под x-editable с дополнительной возможностью добавлять объекты на лету

в 18:24, , рубрики: AngularJS, ui-select, xeditable

Здравствуйте!

Недавно мне довелось адаптировать ui-select под x-editable в Ангуляре и поскольку для этого пришлось потратить определенное количество времени, собирая по крупицам наиболее приемлемый вариант, сегодня я решил поделиться своими наработками с вами, в надежде на то, что кому-нибудь это сэкономит время.

Если вкратце, то полученная в итоге директива замещает стандартный editable-select, плюс дополнительная возможность добавлять объекты на лету.

Теперь подробнее.

Для начала начну с конца и приведу код директивы, позволяющей добавить кнопку в ui-select. На эту кнопку вешается функция, в моем случае — функция, вызывающая модальное окно(ui-bootstrap modal) с формой добавления нового объекта:

app.directive("addNewItem", function($timeout) {
    return {
        restrict: "A",
        link: function(scope, e, attrs) {
            var method = attrs.addNewItem;
            var template = "<div class='add-new-item-container'>"+
                           "<button class='btn btn-xs btn-default pull-right'>Добавить</button>"+
                           "<div class='clearfix'></div></div>";

            e.find('li.ui-select-choices-group').append(template);

            e.find('div.add-new-item-container button').bind('click', function() {
                // workaround for closing ui-select
                $timeout(function() {
                    e.trigger("click");
                });

                var searchResult = e.find('li.ui-select-choices-row').length;

                if ( ! searchResult ) {
                    var value = e.find('input.ui-select-search').val();
                    scope[method].apply(null, [value]);
                } else {
                    scope[method].apply();
                }

            });
        }
    }
})

В принципе, код предельно прост, поэтому остановлюсь на паре моментов. Во-первых, ui-select не закрывается после нажатия кнопки, но закрывается, если кликнуть куда-нибудь по модальному окну. Поэтому пришлось добавить костыль с $timeout.
Во-вторых, (по поводу последнего if else): если в поиске ui-select не найдено ни одного элемента, я передаю значение поиска в метод, чтобы в дальнейшем автозаполнить этим значением форму и таким образом немного сэкономить время.

Теперь основная директива:

app.directive('editableUiSelect', 
        ['editableDirectiveFactory', 'editableNgOptionsParser', 
        function(editableDirectiveFactory, editableNgOptionsParser) {
            var dir = editableDirectiveFactory({
                directiveName: 'editableUiSelect',
                inputTpl: '<ui-select></ui-select>',
                render: function() {
                    this.parent.render.call(this);
                    var parsed = editableNgOptionsParser(this.attrs.eNgOptions);

                    this.inputEl.attr('ng-model', 'editable.entity');
                    // слева директива, описанная вначале, 
                    // справа - метод, в моем случае вызывающий модальное окно с формой добавления
                    this.inputEl.attr('add-new-item', 'addNewItem');
                    // поскольку модель самостоятельно не меняется, пришлось добавить этот метод
                    this.inputEl.attr('on-select', 'setModel($item)');
                    this.inputEl.attr('theme', 'select2');
                    this.inputEl.css('width', '200px');

                    var html = "<ui-select-match><span ng-bind='$select.selected.name'></span></ui-select-match>"+
                               "<ui-select-choices repeat='" + parsed.ngRepeat + "'>" +
                               "<span ng-bind-html='" + parsed.locals.displayFn + "'></span></ui-select-choices>";

                    this.inputEl.removeAttr('ng-options');
                    this.inputEl.append(html);
                }
            });

            return dir;

        }]);

Директива была сделана по образу и подобию стандартных директив xeditable, а потом немного переделана.
Одной из основных проблем, с которыми я столкнулся при написании директивы, была невозможность изменить модель, поэтому был добавлен дополнительный метод для on-select.

Использовать все это можно так:

<span
    data-pk="{{ entity.id }}"
    editable-ui-select="entity.property"
    ng-model="entity.property"
    e-ng-options="obj.id as obj.name for obj in objects | filter: $select.search track by obj.id"
    e-form="rowform">
    {{ entity.property.name }}
</span>

И напоследок бонус. Если использовать это дело в table-responsive (bootstrap), то выпадающий список может быть перекрыт таблицей (особенно, если она состоит из пары строк). Исправить это можно, добавив css:

.table-responsive .ui-select-dropdown {
    position: relative !important;
}

Заключение. Вариантов реализации данной директивы может быть множество и наверняка среди них есть варианты лучше моего. Я опубликовал свои наработки только потому, что в официальной документации xeditable пока нету упоминания о поддержке ui-select, а также потому, что я нашел мало информации по теме, а та, что нашел, разнится между собой.

Надеюсь на то, что гуру ангуляра помогут мне улучшить мою директиву, а также на то, что кому-нибудь пригодится эта статья.

Автор: ivanuzzo

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js