Подтвердить что ты не робот

Обертывание входных данных в директивы в angular

У меня возникла идея поместить ввод в пользовательские директивы, чтобы гарантировать последовательный внешний вид и поведение на моем сайте. Я также хочу, чтобы упаковать бутстрап ui datepicker и выпадающий список. Кроме того, директива должна обрабатывать подсказки и отображать всплывающие подсказки.

HTML должен выглядеть примерно так:

<my-input required max-length='5' model='text' placeholder='text' name='text'/>

или

<my-datepicker required model='start' placeholder='start' name='start'/>

в директивах я хочу создать структуру dom, например:

<div>
 <div>..</div> //display validation in here
 <div>..</div> //add button to toggle datepicker (or other stuff) in here
 <div>..</div> //add input field in here
</div>

Я пробовал различные способы достижения этого, но всегда сталкивался с некоторыми компромиссами:

  • используя transclude и replace, чтобы вставить вход в структуру dom директив (в этом случае директива будет ограничена "A", а не "E", как в примере выше). Проблема здесь в том, что нет простого способа доступа к элементу transcluded, поскольку я хочу добавить пользовательские атрибуты в случае datepicker. Я мог бы использовать функцию transclude, а затем перекомпилировать шаблон в функции ссылок, но это кажется немного сложным для этой задачи. Это также приводит к проблемам с транскодированной областью и состоянием переключения для datepicker (один из них находится в области директив, другой - в области трансляции).

  • используя только замену. В этом случае все атрибуты применяются к внешнему div (даже если я сгенерирую структуру шаблона dom в функции компиляции). Если я использую только вход в качестве шаблона, то атрибуты находятся на входе, но мне нужно сгенерировать шаблон в функции ссылок, а затем перекомпилировать его. Насколько я понимаю фазовую модель angular, я хотел бы избежать перекомпиляции и изменения шаблона dom в функции ссылки (хотя я видел, как многие это делали).

В настоящее время я работаю со вторым подходом и создаю шаблон в функции ссылок, но мне было интересно, есть ли у кого-то лучшие идеи!

4b9b3361

Ответ 1

Вот что я считаю правильным способом сделать это. Подобно OP, я хотел иметь возможность использовать директиву атрибута для оболочки input. Но я также хотел, чтобы он работал с ng-if и без каких-либо элементов. Как отметил @jantimon, если вы не очистите свои элементы оболочки, они будут задерживаться после ng - если уничтожит исходный элемент.

app.directive("checkboxWrapper", [function() {
    return {
      restrict: "A",
      link: function(scope, element, attrs, ctrl, transclude) {
        var wrapper = angular.element('<div class="wrapper">This input is wrappered</div>');

        element.after(wrapper);
        wrapper.prepend(element);

        scope.$on("$destroy", function() {
          wrapper.after(element);
          wrapper.remove();
        });
      }
    };
  }
]);

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

ВАЖНО: scope vs element $destroy. Вы должны поместить свою очистку в scope.$on("$destroy"), а не в element.on("$destroy") (это то, что я изначально пытался). Если вы сделаете это в последнем (элементе), то тег комментария "ngIf end" будет просочиться. Это связано с тем, что Angular ngIf идет об очистке своего тега конца комментария, когда он выполняет свою ложную логику. Поместив ваш код очистки директивы в область $destroy, вы можете поместить DOM обратно, как это было до того, как вы обернули ввод, и поэтому ng-if код очистки будет счастлив. К моменту вызова элемента .on( "$ destroy" ) слишком поздно в потоке ng-if false, чтобы развернуть исходный элемент, не вызывая утечку тега комментария.

Ответ 2

Почему бы не сделать такую ​​директиву?

myApp.directive('wrapForm', function(){
    return {
        restrict: 'AC',
        link: function(scope, inputElement, attributes){                       
            var overallWrap = angular.element('<div />');
            var validation = angular.element('<div />').appendTo(overallWrap);
            var button = angular.element('<div />').appendTo(overallWrap);
            var inputWrap = angular.element('<div />').appendTo(overallWrap);

            overallWrap.insertBefore(inputElement);
            inputElement.appendTo(inputWrap);

            inputElement.on('keyup', function(){
                if (inputElement.val()) {
                    validation.text('Just empty fields are valid!');
                } else {
                    validation.text('');
                }
            });            
        }
    }
});

Fiddle: http://jsfiddle.net/bZ6WL/

В основном вы берете исходное поле ввода (это, кстати, также директива angularjs) и создавайте обертки отдельно. В этом примере я просто создаю DIVs вручную. Для более сложных вещей вы также можете использовать шаблон, который получает $compile (d) by angularjs.

Преимущество использования этого класса или атрибута html "wrapForm": вы можете использовать ту же директиву для нескольких типов ввода типов.

Ответ 3

Почему бы не обернуть ввод в функцию компиляции? Преимущество состоит в том, что вам не придется копировать атрибуты и не придется очищать функцию уничтожения области. Обратите внимание, что вам нужно удалить атрибут директивы, чтобы предотвратить циклическое выполнение.

(http://jsfiddle.net/oscott9/8er3fu0r/)

angular.module('directives').directive('wrappedWithDiv', [
    function() {
        var definition = {
            restrict: 'A',
            compile: function(element, attrs) {
                element.removeAttr("wrapped-with-div");
                element.replaceWith("<div style='border:2px solid blue'>" +
                    element[0].outerHTML + "</div>")
            }
        }
        return definition;
    }
]);

Ответ 4

Исходя из этого: http://angular-tips.com/blog/2014/03/transclusion-and-scopes/

Эта директива делает переключение, но transcluded stuff использует родительскую область, поэтому все привязки работают так, как если бы содержимое, заключенное в transcluded, находилось в исходной области, где используется оболочка. Это, конечно, включает ng-model, также min/max и другие директивы/атрибуты проверки. Должен работать на любой контент. Я не использую директиву ng-transclude, потому что я вручную клонирую элементы и предоставляю им родительскую (контрольную) область. "my-transclude" используется вместо ng-transclude, чтобы указать, куда вставлять транслируемый контент.

Слишком плохо ng-transclude не имеет настройки для контроля области. Это сделало бы все это неудобство ненужным. И похоже, что они не исправят это: https://github.com/angular/angular.js/issues/5489

    controlsModule.directive('myWrapper', function () {
        return {
            restrict: 'E',
            transclude: true,
            scope: {
                label: '@',
                labelClass: '@',
                hint: '@'
            },

            link: link,
            template:
                '<div class="form-group" title="{{hint}}"> \
                    <label class="{{labelClass}} control-label">{{label}}</label> \
                    <my-transclude></my-transclude> \
                 </div>'
        };

        function link(scope, iElement, iAttrs, ctrl, transclude) {

            transclude(scope.$parent,
                function (clone, scope) {

                    iElement.find("my-transclude").replaceWith(clone);

                    scope.$on("$destroy", function () {
                        clone.remove();
                    });
                });
        }
    });