Solução alternativa de preenchimento automático do navegador AngularJS usando uma diretiva


111

Ao enviar um formulário em AngularJS e usar a funcionalidade de lembrar senha do navegador e, em uma tentativa de login subsequente, você permitir que o navegador preencha o formulário de login com o nome de usuário e a senha, o $scopemodelo não será alterado com base no preenchimento automático.

O único hack sujo que encontrei é usar a seguinte diretiva:

app.directive("xsInputSync", ["$timeout" , function($timeout) {
    return {
        restrict : "A",
        require: "?ngModel",
        link : function(scope, element, attrs, ngModel) {
            $timeout(function() {
                if (ngModel.$viewValue && ngModel.$viewValue !== element.val()) {
                    scope.apply(function() {
                        ngModel.$setViewValue(element.val());
                    });
                }
                console.log(scope);
                console.log(ngModel.$name);
                console.log(scope[ngModel.$name]);
            }, 3000);
        }
    };
}]);

O problema é que o ngModel.$setViewValue(element.val());não altera o modelo nem a visualização com base no element.val()valor retornado. Como posso fazer isso?


Este código parece bom à primeira vista ... mas onde está o resto (marcação, etc)? Você tem um violino ou plunk que possamos ver?
Ben Lesh

Aqui está o plunker : plnkr.co/edit/CHrBAVU9Ycl2ex2DRr6R Não tenho certeza se funciona diretamente no plunker porque é executado em um iframe.
lucassp

1
Você não precisa definir o escopo. $ Apply dentro do $ timeout do angular. Você pode precisar dele dentro de window.setTimeout nativo. Mas é uma ideia melhor usar o angular.
gorpacrate

1
Há uma correção "oficial" do polyfill do Angular dev tbosch para esse problema. Consulte os detalhes na resposta stackoverflow.com/a/25687396/3009639 abaixo.
orszaczky

Infelizmente, a correção "oficial" é abandonware e não funciona em muitos navegadores. Os problemas foram registrados, mas não foram solucionados, então a busca continua ...
cyberwombat

Respostas:


45

Aparentemente, este é um problema conhecido do Angular e está aberto no momento

Não tenho certeza do que você poderia fazer aqui além de algum tipo de solução que está tentando. Parece que você está no caminho certo. Não consegui fazer meu navegador tentar lembrar a senha do seu plunk, então não tenho certeza se isso vai funcionar, mas dê uma olhada:

app.directive('autoFillSync', function($timeout) {
   return {
      require: 'ngModel',
      link: function(scope, elem, attrs, ngModel) {
          var origVal = elem.val();
          $timeout(function () {
              var newVal = elem.val();
              if(ngModel.$pristine && origVal !== newVal) {
                  ngModel.$setViewValue(newVal);
              }
          }, 500);
      }
   }
});
<form name="myForm" ng-submit="login()">
   <label for="username">Username</label>
   <input type="text" id="username" name="username" ng-model="username" auto-fill-sync/><br/>
   <label for="password">Password</label>
   <input type="password" id="password" name="password" ng-model="password" auto-fill-sync/><br/>
   <button type="submit">Login</button>
</form>

Acho que você só precisa simplificar um pouco sua abordagem. A única coisa que eu definitivamente recomendo é verificar ngModel.$pristinee certificar-se de que você não está substituindo a entrada de algum usuário pobre. Além disso, 3 segundos provavelmente é muito tempo. Você não deveria ter que chamar $ apply () em um $ timeout, BTW, ele deveria enfileirar um $ digest para você automaticamente.

O verdadeiro problema: seu navegador vai bater o Angular na execução? E o meu navegador?

Esta é provavelmente uma guerra impossível de vencer, e é por isso que Angular (ou Knockout) não foi capaz de resolvê-la prontamente. Não há garantia do estado dos dados em sua entrada no momento da execução inicial da diretiva. Nem mesmo no momento da inicialização do Angular ... Portanto, é um problema complicado de resolver.


1
Boa observação sobre $ pristine. Bem, 3 segundos são apenas para fins de teste. A caixa de diálogo "Salvar senha" parece funcionar no Safari. Esse é outro problema que tenho que investigar.
lucassp

Basta enganá-lo com armazenamento local. :) ... Vou continuar pensando nisso. Mas tenho minhas dúvidas sobre a viabilidade de fazer o preenchimento automático funcionar com quaisquer ligações bidirecionais.
Ben Lesh

Sim, mas ainda preciso ser capaz de atualizar o modelo de uma diretiva para que funcione.
lucassp

1
Eu estava brincando sobre localStorage, você não gostaria de manter as senhas lá.
Ben Lesh

1
Isso não funcionará com o Safari e com o preenchimento automático do cartão de crédito, pois esse preenchimento automático é acionado a qualquer momento pelo usuário
Dallas Clark,

35

Aqui está uma solução que é muito menos hacky do que outras soluções apresentadas e é semanticamente adequada para AngularJS: http://victorblog.com/2014/01/12/fixing-autocomplete-autofill-on-angularjs-form-submit/

myApp.directive('formAutofillFix', function() {
  return function(scope, elem, attrs) {
    // Fixes Chrome bug: https://groups.google.com/forum/#!topic/angular/6NlucSskQjY
    elem.prop('method', 'POST');

    // Fix autofill issues where Angular doesn't know about autofilled inputs
    if(attrs.ngSubmit) {
      setTimeout(function() {
        elem.unbind('submit').submit(function(e) {
          e.preventDefault();
          elem.find('input, textarea, select').trigger('input').trigger('change').trigger('keydown');
          scope.$apply(attrs.ngSubmit);
        });
      }, 0);
    }
  };
});

Em seguida, basta anexar a diretiva ao seu formulário:

<form ng-submit="submitLoginForm()" form-autofill-fix>
  <div>
    <input type="email" ng-model="email" ng-required />
    <input type="password" ng-model="password" ng-required />
    <button type="submit">Log In</button>
  </div>
</form>

2
Este funcionou. Tentamos a maioria das opções acima sem nenhum resultado. Obrigado!!! O resultado está em mobbr.com
Patrick Savalle

1
Como ele usa jQuery, não se espera que funcione sem jQuery.
Quinn Strahl

1
Aqui estamos nós em 2018 e esta ainda é a solução. Obrigado Ezequiel!
Scott Manny

35

Você não precisa usar um $timeoutou algo assim. Você pode usar um sistema de eventos.

Acho que é mais angular e não depende de jQuery ou captura de evento personalizado.

Por exemplo, em seu manipulador de envio:

$scope.doLogin = function() {
    $scope.$broadcast("autofill:update");

    // Continue with the login.....
};

E então você pode ter uma autofilldiretiva como esta:

.directive("autofill", function () {
    return {
        require: "ngModel",
        link: function (scope, element, attrs, ngModel) {
            scope.$on("autofill:update", function() {
                ngModel.$setViewValue(element.val());
            });
        }
    }
});

Finalmente, seu HTML será como:

<input type="text" name="username" ng-model="user.id" autofill="autofill"/>

14
No meu caso, o botão enviar está desabilitado enquanto as entradas estão vazias.
gorpacrate

4
você nunca tem problemas com isso, pois é assíncrono? onde a chamada de login para o servidor já sai antes de o evento ser replicado e a diretiva ter tempo de atualizar o escopo?
Sander

12

Não há necessidade de hackear mais! Angular dev tbosch fez um polyfill que aciona um evento de alteração quando o navegador altera os campos do formulário sem acionar um evento de alteração:

https://github.com/tbosch/autofill-event

Por enquanto, eles não vão construir isso no código Angular, já que esta é uma correção de bug para o navegador e também funciona sem Angular (por exemplo, para aplicativos jQuery simples).

"O polyfill verificará as alterações no carregamento do documento e também quando uma entrada for deixada (apenas no mesmo formulário). No entanto, você pode acionar a verificação manualmente se desejar.

O projeto tem testes de unidade, bem como testes semiautomáticos, então finalmente temos um lugar para coletar todos os diferentes casos de uso junto com as configurações de navegador necessárias.

Observação: este polyfill funciona com aplicativos AngularJS simples, com aplicativos AngularJS / jQuery, mas também com aplicativos jQuery simples que não usam Angular. "

Pode ser instalado com:

bower install autofill-event --save

Adicione o script autofill-event.js após jQuery ou Angular em sua página.

Isso fará o seguinte:

  • após DOMContentLoaded: verifique todos os campos de entrada
  • um campo é deixado: verifique todos os outros campos no mesmo formulário

API (para acionar manualmente a verificação):

  • $el.checkAndTriggerAutoFillEvent(): Execute a verificação de todos os elementos DOM no elemento jQuery / jQLite fornecido.

Como funciona

  1. Lembre-se de todas as mudanças nos elementos de entrada pelo usuário (ouvindo eventos de mudança) e também por JavaScript (interceptando $ el.val () para elementos jQuery / jQLite). Esse valor alterado é armazenado no elemento em uma propriedade privada.

  2. Verificando um elemento para preenchimento automático: Compare o valor atual do elemento com o valor lembrado. Se for diferente, acione um evento de mudança.

Dependências

AngularJS ou jQuery (funciona com um ou ambos)

Mais informações e fontes na página do github .

A edição original do Angular nº 1460 no Github pode ser lida aqui.


2
Isso não funciona para meus casos. checkAndTriggerAutoFillEvent () é disparado e gera eventos, mas AngularJS nunca atualiza seus modelos e pensa que os campos estão vazios.
Splaktar

Não tive problemas ao usar este polyill. Se você não conseguir fazer com que funcione corretamente, deve criar uma pergunta separada incluindo algum código. Se você encontrar um bug, deve verificar os tíquetes no github ou abrir um novo.
orszaczky

Sim, acho que está relacionado a este bug: github.com/tbosch/autofill-event/issues/11
Splaktar

1
ngModelOptionscombinado com autofill-eventsignifica que agora você pode especificar changecomo um dos updateOneventos para garantir que seu modelo esteja sincronizado corretamente.
morloch,

10

Código sujo, verifique se o problema https://github.com/angular/angular.js/issues/1460#issuecomment-18572604 foi corrigido antes de usar este código. Esta diretiva dispara eventos quando o campo é preenchido, não apenas antes do envio (é necessário se você tiver que lidar com a entrada antes do envio)

 .directive('autoFillableField', function() {
    return {
                   restrict: "A",
                   require: "?ngModel",
                   link: function(scope, element, attrs, ngModel) {
                       setInterval(function() {
                           var prev_val = '';
                           if (!angular.isUndefined(attrs.xAutoFillPrevVal)) {
                               prev_val = attrs.xAutoFillPrevVal;
                           }
                           if (element.val()!=prev_val) {
                               if (!angular.isUndefined(ngModel)) {
                                   if (!(element.val()=='' && ngModel.$pristine)) {
                                       attrs.xAutoFillPrevVal = element.val();
                                       scope.$apply(function() {
                                           ngModel.$setViewValue(element.val());
                                       });
                                   }
                               }
                               else {
                                   element.trigger('input');
                                   element.trigger('change');
                                   element.trigger('keyup');
                                   attrs.xAutoFillPrevVal = element.val();
                               }
                           }
                       }, 300);
                   }
               };
});

5

Parece uma solução clara e direta. Não é necessário jQuery.

ATUALIZAR:

  • O modelo é atualizado apenas quando o valor do modelo não é igual ao valor de entrada real.
  • A verificação não para no primeiro preenchimento automático. No caso de desejar usar outra conta, por exemplo.

app.directive('autofillable', ['$timeout', function ($timeout) {
    return {
        scope: true,
        require: 'ngModel',
        link: function (scope, elem, attrs, ctrl) {
            scope.check = function(){
                var val = elem[0].value;
                if(ctrl.$viewValue !== val){
                    ctrl.$setViewValue(val)
                }
                $timeout(scope.check, 300);
            };
            scope.check();
        }
    }
}]);

4
1 boa solução, semelhante ao que eu vim. A única adição que eu sugeriria é primeiro verificar se o modelo está $pristineantes de alterá-lo e, depois de alterá-lo, mantenha seu $pristineestado. Caso contrário, você descobrirá que se um formulário for carregado sem dados de preenchimento automático, a diretiva acima ainda atualizará o modelo e, subsequentemente, o fará, dirtyembora o controle do formulário ainda deva ser considerado intocado. exemplo aqui, usando sua diretiva. Comentei o código que adicionaria a ele: jsfiddle.net/OACDesigns/L8Sja/4
OACDesigns

1
também, acho que scope:truedeveria serscope:{}
OACDesigns

4

Solução 1 [usando $ timeout]:

Diretriz:

app.directive('autoFillSync', function($timeout) {
    return {
      require: 'ngModel',
      link: function(scope, elem, attrs, model) {
          var origVal = elem.val();
          $timeout(function () {
              var newVal = elem.val();
              if(model.$pristine && origVal !== newVal) {
                  model.$setViewValue(newVal);
              }
          }, 500);
      }
    };
});

HTML:

<form name="myForm" ng-submit="login()">
  <label for="username">Username</label>
  <input type="text" id="username" name="username" ng-model="username" auto-fill-sync/><br/>
  <label for="password">Password</label>
  <input type="password" id="password" name="password" ng-model="password" auto-fill-sync/><br/>
  <button type="submit">Login</button>
</form>

Solução 2 [Usando eventos angulares]:

Ref: a resposta de Becko

Diretriz:

app.directive("autofill", function () {
    return {
        require: "ngModel",
        link: function (scope, element, attrs, ngModel) {
            scope.$on("autofill:update", function() {
                ngModel.$setViewValue(element.val());
            });
        }
    };
});

HTML:

<form name="myForm" ng-submit="login()">
  <label for="username">Username</label>
  <input type="text" id="username" name="username" ng-model="username" autofill/><br/>
  <label for="password">Password</label>
  <input type="password" id="password" name="password" ng-model="password" autofill/><br/>
  <button type="submit">Login</button>
</form>

Solução 3 [Usando chamadas de método de retransmissão]:

Diretriz:

app.directive('autoFill', function() {
    return {
        restrict: 'A',
        link: function(scope,element) {
            scope.submit = function(){
                scope.username = element.find("#username").val();
                scope.password = element.find("#password").val();
                scope.login();//call a login method in your controller or write the code here itself
            }

        }
    };
});

HTML:

<form name="myForm" auto-fill ng-submit="submit()">
   <label for="username">Username</label>
   <input type="text" id="username" name="username" ng-model="username" />
   <label for="password">Password</label>
   <input type="password" id="password" name="password" ng-model="password" />
   <button type="submit">Login</button>
</form>

2
A solução 1 funcionou muito bem por um tempo, mas agora no angularjs 1.4.9 ela não funciona mais. A verificação model.$validda diretiva retorna true antes e depois $setViewValue, mas o elemento ainda está com a ng-invalidclasse
John

4

Bem, a maneira mais fácil é emular o comportamento do navegador, então se houver um problema com o evento de mudança, basta dispará-lo você mesmo. Muito mais simples.

Diretriz:

yourModule.directive('triggerChange', function($sniffer) {
    return {
        link : function(scope, elem, attrs) {
            elem.on('click', function(){
                $(attrs.triggerChange).trigger(
                    $sniffer.hasEvent('input') ? 'input' : 'change'
                );
            });
        },
        priority : 1
    }
});

HTML:

<form >
    <input data-ng-model="user.nome" type="text" id="username">

    <input data-ng-model="user.senha" type="password" id="password" >

    <input type="submit" data-ng-click="login.connect()" id="btnlogin" 
           data-trigger-change="#password,#username"/>
</form>

Você pode fazer algumas variações, como colocar a diretiva no formulário e disparar o evento em todas as entradas com a classe .dirty no envio do formulário.


3

Este é o jeito jQuery:

$(window).load(function() {
   // updates autofilled fields
   window.setTimeout(function() {
     $('input[ng-model]').trigger('input');
   }, 100);
 });

Esta é a maneira angular:

 app.directive('autofill', ['$timeout', function ($timeout) {
    return {
        scope: true,
        require: 'ngModel',
        link: function (scope, elem, attrs, ctrl) {
            $timeout(function(){
                $(elem[0]).trigger('input');
                // elem.trigger('input'); try this if above don't work
            }, 200)
        }
    }
}]);

HTML

<input type="number" autofill /> 

2
Forma totalmente não angular.
gorpacrate

1
@gorpacrate: Agora verifique de forma angular.
Nishchit Dhanani

2
As guias do Angular Way têm muitos espaços. Apenas brincando. Eu amo rolagem horizontal.
Daniel Birowsky Popeski

isso não funciona no angular, pois não há gatilho de função no elemento. Eu acho que isso exigiria incluir jquery
Tomas

1
triggerHandler('input')deve estar ok se você não tiver jquery desenvolvido
Mutmatt

1

Aqui está outra solução alternativa que é menos hacky, mas requer algum código extra no controlador.

HTML:

<form ng-submit="submitForm()" ng-controller="FormController">
    <input type="text" ng-model="username" autocomplete-username>
    <input type="submit">
</form>

Diretiva (CoffeeScript):

directives.directive 'autocompleteUsername', ->
    return (scope, element) ->
        scope.getUsername = ->
            element.val()

Controlador:

controllers.controller 'FormController', [->
    $scope.submitForm = ->
        username = $scope.getUsername?() ? $scope.username
        # HTTP stuff...
]

Você tem isso no vanilla JavaScript?
f1lt3r

CoffeeScript.org fornece um tradutor: feito aqui
jab

1

Esta é a única solução que encontrei que permitiu que todas as minhas validações Angular funcionassem conforme planejado, incluindo o botão de habilitar / desabilitar. Instala com caramanchão e 1 tag de script. Bazinga!

https://github.com/tbosch/autofill-event


1
este preenchimento de poli não se encaixa com um botão de envio / ligação de entrada para o estado do formulário. a menos que você clique na página em qualquer ponto em que deseja acionar um resumo (parece que a ação aciona o navegador para fazer um enchimento real), o botão se torna habilitado. Página de teste com testes manuais
e-cloud

1

Alterar o valor do modelo, em vez de usar uma função de tempo limite, funcionou para mim.

Aqui está o meu código:

module.directive('autoFill', [ function() {
    return {
        require: 'ngModel',
        link:function(scope, element, attr, ngModel) {
            var origVal = element.val();
            if(origVal){
                ngModel.$modelValue = ngModel.$modelValue || origVal;
            }
        }
    };
}]);

1

Solução alternativa de uma linha no manipulador de envio (requer jQuery):

if (!$scope.model) $scope.model = $('#input_field').val();

Não é realmente uma linha se estiver em uma diretiva, o que certamente deveria ser.
isherwood de

0

Eu forço um $ setValue (val ()) no envio: (funciona sem jQuery)

   var ValidSubmit = ['$parse', function ($parse) {
    return {
        compile: function compile(tElement, tAttrs, transclude) {
            return {
                post: function postLink(scope, element, iAttrs, controller) {
                    var form = element.controller('form');
                    form.$submitted = false;
                    var fn = $parse(iAttrs.validSubmit);
                    element.on('submit', function(event) {
                        scope.$apply(function() {
                            var inputs = element.find('input');
                            for(var i=0; i < inputs.length; i++) {
                                var ele = inputs.eq(i);
                                var field = form[inputs[i].name];
                                field.$setViewValue(ele.val());
                            }
                            element.addClass('ng-submitted');
                            form.$submitted = true;
                            if(form.$valid) {
                                fn(scope, {$event:event});
                            }
                        });
                    });
                    scope.$watch(function() { return form.$valid}, function(isValid) {
                        if(form.$submitted == false) return;
                        if(isValid) {
                            element.removeClass('has-error').addClass('has-success');
                        } else {
                            element.removeClass('has-success');
                            element.addClass('has-error');
                        }
                    });
                }
            }
        }
    }
}]
app.directive('validSubmit', ValidSubmit);

0

Eu sou muito novo no Angularjs, mas encontrei uma solução simples para esse problema => Forçar o Angular a reavaliar a expressão ... mudando-a! (é claro que você precisa se lembrar do valor inicial para reverter para o estado inicial). Esta é a maneira como funciona na função do controlador para enviar o formulário:

    $scope.submit = function () {
                var oldpassword = $scope.password;
                $scope.password = '';
                $scope.password = oldpassword;
//rest of your code of the submit function goes here...

onde, é claro, o valor inserido em sua entrada de senha foi definido pelo Windows e não pelo usuário.


0

Você pode tentar este código:

yourapp.directive('autofill',function () {

    return {
        scope: true,
        require: 'ngModel',
        link: function (scope, elem, attrs, ctrl) {
            var origVal = elem.val();
            if (origVal != '') {
                elem.trigger('input');
            }
        }
    }
});

0

Uma pequena modificação nesta resposta ( https://stackoverflow.com/a/14966711/3443828 ): use um intervalo $ em vez de um $ timeout para não ter que correr contra o navegador.

mod.directive('autoFillSync', function($interval) {
    function link(scope, element, attrs, ngModel) {
        var origVal = element.val();
        var refresh = $interval(function() {
          if (!ngModel.$pristine) {
            $interval.cancel(refresh);
          }else{
            var newVal = element.val();
            if (origVal !== newVal) {
              ngModel.$setViewValue(newVal);
              $interval.cancel(refresh);
            }
          }
        }, 100);
    }

    return {
      require: 'ngModel',
      link: link
    }
  });

0

Essa é a solução que acabei usando nos meus formulários.

.directive('autofillSync', [ function(){
  var link = function(scope, element, attrs, ngFormCtrl){
    element.on('submit', function(event){
      if(ngFormCtrl.$dirty){
        console.log('returning as form is dirty');
        return;
      }   
      element.find('input').each(function(index, input){
        angular.element(input).trigger('input');
      }); 
    }); 
  };  
  return {
    /* negative priority to make this post link function run first */
    priority:-1,
    link: link,
    require: 'form'
  };  
}]);

E o modelo do formulário será

<form autofill-sync name="user.loginForm" class="login-form" novalidate ng-submit="signIn()">
    <!-- Input fields here -->
</form>

Desta forma, consegui executar qualquer analisador / formatador que tenho em meu ng-model e ter a funcionalidade de envio transparente.


0

Solução sem diretivas:

.run(["$window", "$rootElement", "$timeout", function($window, $rootElement, $timeout){

        var event =$window.document.createEvent("HTMLEvents");
        event.initEvent("change", true, true);

        $timeout(function(){

            Array.apply(null, $rootElement.find("input")).forEach(function(item){
                if (item.value.length) {
                    item.$$currentValue = item.value;
                    item.dispatchEvent(event);
                }
            });

        }, 500);
    }])

0

Esta é uma correção simples que funciona para todos os casos que testei no Firefox e no Chrome. Observe que com a resposta principal (diretiva com tempo limite), tive problemas com -

  • Botões voltar / avançar do navegador, não disparar novamente eventos de carregamento de página (portanto, a correção não se aplica)
  • Carregamento de credenciais algum tempo após o carregamento da página. por exemplo, no Firefox, clique duas vezes na caixa de login e selecione uma das credenciais armazenadas.
  • Preciso de uma solução que atualize antes do envio do formulário, já que desativo o botão Login até que uma entrada válida seja fornecida

Essa correção é obviamente muito burra e hacky, mas funciona 100% do tempo -

function myScope($scope, $timeout) {
    // ...
    (function autoFillFix() {
        $timeout(function() { 
            $('#username').trigger('change'); 
            $('#password').trigger('change'); 
            autoFillFix(); }, 500);                    
    })();
}

1
poderia substituir $timeoutcom $intervalpara dar futuro devs a mais contexto pouco e se livrar dos que procuram estranho chamadas recursivas acontecendo lá
Mutmatt

0

Nenhuma dessas soluções funcionou para meu caso de uso. Eu tenho alguns campos de formulário que usam ng-change para observar mudanças. Usando$watch não ajuda, pois não é acionado pelo preenchimento automático. Como não tenho o botão de enviar, não há uma maneira fácil de executar algumas das soluções e não obtive sucesso usando intervalos.

Acabei desabilitando o preenchimento automático - não é o ideal, mas muito menos confuso para o usuário.

<input readonly onfocus="this.removeAttribute('readonly');">

Encontrou a resposta aqui


-1

Se você estiver usando jQuery, você pode fazer isso no envio do formulário:

HTML:

<form ng-submit="submit()">
    <input id="email" ng-model="password" required 
           type="text" placeholder="Your email">
    <input id="password" ng-model="password" required 
           type="password" placeholder="Password">
</form>

JS:

 $scope.submit = function() {
     $scope.password = $('#password').val();
}

1
Embora isso possa funcionar, não é razoável para formulários mais complexos.
origin1tech

-1

Se você quiser mantê-lo simples, obtenha o valor usando javascript

Em seu controlador js angular:

var username = document.getElementById ('username'). value;


Acho que você não entende qual é o problema
aelor em
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.