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

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

    Недавно мне довелось адаптировать 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, а также потому, что я нашел мало информации по теме, а та, что нашел, разнится между собой.

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

    UPD. Небольшой пример: plnkr.co/edit/5dPKCnzbKE8D9XIR8ocX?p=preview
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 6
    • +2
      Рука тянется к минусу, т.к. не нахожу ни примера ни картинок.
      • –1
        сейчас сделаю пример
        • 0
          Получилось примерно такое plnkr.co/edit/eyXgkpABHmBSW9bGwGVp, но, к сожалению, некоторые тонкости планкера от меня пока сокрыты, поэтому кое-что я завести там так и не смог. В частности, ругается на некоторые jquery-функции, не смотря на то, что jquery я подключил, плюс не вызывается модальное окно.

          Возможно, я что-то упустил.
        • +1
          link: function(scope, e, attrs) {
          

          «e» — это имеется ввиду «element»? Часто «e» используется как «event», поэтому такое название переменной может ввести в ступор.
          plnkr.co/edit/Ds3A6srJVo65FD7GWPTz — демо немного поправил. Не очень хорошо, что когда появляется ui-select в нём нет выбранного значения.
          • 0
            о, спасибо. Да, вы правы, e — это имелся ввиду element. Учту на будущее замечания.

            Я в свою очередь еще немного подредактировал демку, чтобы из модального окна можно было чего-нибудь добавлять: plnkr.co/edit/5dPKCnzbKE8D9XIR8ocX?p=preview
            • +1
              может, кому-то пригодится: сделал, чтобы при клике на ui-select отображался выделенный элемент, а не пустота plnkr.co/edit/5dPKCnzbKE8D9XIR8ocX?p=preview. Вкратце, для этого в модель добавилось свойство selected, которое используется в самом ui-select'е + соответственно изменилась инициализация дефолтного значения в контроллере.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.