Запустить функцию javascript, когда пользователь закончит ввод, а не при нажатии клавиши?


464

Я хочу, чтобы вызвать запрос Ajax, когда пользователь закончил вводить текстовое поле. Я не хочу, чтобы он запускал функцию каждый раз, когда пользователь вводит букву, потому что это приведет к МНОЖЕСТВУ ajax-запросов, однако я не хочу, чтобы они также нажимали кнопку ввода.

Есть ли способ, чтобы я мог определить, когда пользователь закончил печатать, а затем выполнить запрос ajax?

Используя jQuery здесь! Дейв


21
Я думаю, вам нужно определить «закончить печатать» для нас.
Сюрреалистические сны

8
Хотя ответ @Surreal Dreams удовлетворяет большинству ваших требований, если пользователь начинает вводить снова ПОСЛЕ указанного времени ожидания, на сервер будет отправлено несколько запросов. Посмотрите мой ответ ниже, который хранит каждый запрос XHR в переменной и отменяет его перед запуском нового. Это именно то, что Google делает в своем Мгновенном поиске.
Марко

1
возможный дубликат задержки jquery keyup?
CMS

1
Как насчет размытия? Я предполагаю, что пользователь определенно закончил печатать, когда элемент ввода теряет фокус.
Дон П

3
Простой поиск в Google мог бы получить простой ответ: schier.co/blog/2014/12/08/…
Cannicide

Ответы:


689

Итак, я думаю, что завершение набора означает, что вы просто остановитесь на некоторое время, скажем, 5 секунд. Итак, помня об этом, давайте запустим таймер, когда пользователь отпускает клавишу, и сбросим его, когда нажмем одну. Я решил, что рассматриваемый вход будет #myInput.

Делая несколько предположений ...

//setup before functions
var typingTimer;                //timer identifier
var doneTypingInterval = 5000;  //time in ms, 5 second for example
var $input = $('#myInput');

//on keyup, start the countdown
$input.on('keyup', function () {
  clearTimeout(typingTimer);
  typingTimer = setTimeout(doneTyping, doneTypingInterval);
});

//on keydown, clear the countdown 
$input.on('keydown', function () {
  clearTimeout(typingTimer);
});

//user is "finished typing," do something
function doneTyping () {
  //do something
}

4
Спасибо :) Я начал печатать свой комментарий по этому вопросу и понял, что у меня есть хорошая идея.
Сюрреалистические сны

26
Этот ответ не работает правильно, он всегда срабатывает через 5 секунд, если пользователь не наберет очень медленно. Смотрите рабочее решение ниже.
собирается

3
Если у вас возникнут проблемы из-за того, что таймер срабатывает сразу, попробуйте добавить кавычки вокруг вызова функции: setTimeout ('functionToBeCalled', doneTypingInterval);
Ларго

3
Я вижу несколько комментариев, где функция обратного вызова, doneTypingв этом примере, запускается немедленно. Обычно это результат случайного включения ()после имени функции. Обратите внимание, что это должно читаться setTimeout(doneTyping,неsetTimeout(doneTyping(),
Энтони DiSanti

2
@ Джессика, чтобы работать с пастой, вы должны добавить событие «вставить», например: on ('keyup paste',
Сергей

397

Выбранный ответ выше не работает.

Поскольку typingTimer иногда устанавливается несколько раз (нажатие клавиши дважды до нажатия клавиши для быстрого набора и т. Д.), Тогда оно не очищается должным образом.

Приведенное ниже решение решает эту проблему и вызывает X секунд после завершения, как было запрошено OP. Также больше не требуется избыточная функция нажатия клавиш. Я также добавил проверку, чтобы ваш вызов функции не происходил, если ваш ввод пуст.

//setup before functions
var typingTimer;                //timer identifier
var doneTypingInterval = 5000;  //time in ms (5 seconds)

//on keyup, start the countdown
$('#myInput').keyup(function(){
    clearTimeout(typingTimer);
    if ($('#myInput').val()) {
        typingTimer = setTimeout(doneTyping, doneTypingInterval);
    }
});

//user is "finished typing," do something
function doneTyping () {
    //do something
}

И тот же код в ванильном решении JavaScript:

//setup before functions
let typingTimer;                //timer identifier
let doneTypingInterval = 5000;  //time in ms (5 seconds)
let myInput = document.getElementById('myInput');

//on keyup, start the countdown
myInput.addEventListener('keyup', () => {
    clearTimeout(typingTimer);
    if (myInput.value) {
        typingTimer = setTimeout(doneTyping, doneTypingInterval);
    }
});

//user is "finished typing," do something
function doneTyping () {
    //do something
}

Это решение использует ES6, но здесь это не обязательно. Просто замените letна varи функцию стрелки на обычную функцию.


5
Что ж, было бы полезно по-прежнему вызывать функцию, даже если ввод пуст. Что если вы хотите очистить некоторые результаты поиска (например), когда ввод снова пуст?
rvighne

7
Я думаю, что мы должны также рассмотреть условие, когда пользователь просто вставляет строку в поле.
Вишал Наир

10
используйте, $('#myInput').on('input', function() {если вы хотите, чтобы это работало с копированием и вставкой. Я не вижу смысла проверки, если вход пуст. Предположим, что он хочет стереть то, что набрал, это не сможет сделать вызов ajax.
Биллиноа

3
Почему это if ($('#myInput').val)вместо if ($('#myInput').val())? Должна .valбыть функция?
Влнирвана

4
Почему бы не использовать $ (this) .val ()?
JoelFan

76

Это всего лишь одна строка с функцией debounce underscore.js :

$('#my-input-box').keyup(_.debounce(doSomething , 500));

По сути, это говорит о том, что нужно сделать 500 миллисекунд после того, как я перестал печатать.

Для получения дополнительной информации: http://underscorejs.org/#debounce


5
Похоже, что он также доступен для jQuery: code.google.com/p/jquery-debounce и github.com/diaspora/jquery-debounce
koppor

63
Меня удивляет, как люди считают «ОДНУ ЛИНЕЙ КОДЕКСА» критически важной. Отлично. ОДНА ЛИНИЯ кода, для загрузки которого требуется файл JS 1.1k. Я не отказываюсь от этого, потому что это решение. Но смелая вещь в одну строку раздражает меня, а также приводит к раздуванию кода.
Вольф

1
@Wolfie, я согласен с вами по поводу раздувания кода, но Underscore и Lodash - две верхние зависимые утилиты в NPM, поэтому для многих это может быть однострочным решением.
Пол

3
@Paul Я согласен с тем, что если вы загружаете библиотеки для других целей, хорошо и действительно полезно использовать общий код. Но загрузить более K кода для одной строки неэффективно. Особенно с мобильных платформ становится все меньше и меньше безлимитных данных. И многие зоны евро также платят за интернет по данным. Поэтому хорошо обращать внимание на раздувание, когда это целесообразно.
Вольф

почему я получил:Uncaught ReferenceError: _ is not defined
Уилф

44

Да, вы можете установить тайм-аут, скажем, 2 секунды на каждое событие key up, которое вызовет запрос ajax. Вы также можете сохранить метод XHR и прервать его при последующих событиях нажатия клавиш, чтобы еще больше сохранить полосу пропускания. Вот кое-что, что я написал для моего сценария автозаполнения.

var timer;
var x;

$(".some-input").keyup(function () {
    if (x) { x.abort() } // If there is an existing XHR, abort it.
    clearTimeout(timer); // Clear the timer so we don't end up with dupes.
    timer = setTimeout(function() { // assign timer a new timeout 
        x = $.getJSON(...); // run ajax request and store in x variable (so we can cancel)
    }, 2000); // 2000ms delay, tweak for faster/slower
});

Надеюсь это поможет,

Marko


Я бы не рекомендовал это. Код запускает запрос API каждый раз при нажатии клавиши. Затем он отменяет запрос, если нажата другая клавиша. Несмотря на то, что это решение предотвращает запуск обратного вызова AJAX при вводе пользователем, оно все равно будет забивать запросы серверами.
LXG

Черт, нет, этого не будет. Используемая им функция clearTimeout очищает предыдущие события Timeout, поэтому вызывает вызов API.
Джон Смит

@JohnSmith, я не согласен, вероятно, лучше сначала очистить таймер, а затем проверить, пусто ли xhr или что-то еще в вашем состоянии, и, наконец, запустить таймер с помощью вызова ajax
Fleuv

20
var timer;
var timeout = 1000;

$('#in').keyup(function(){
    clearTimeout(timer);
    if ($('#in').val) {
        timer = setTimeout(function(){
            //do stuff here e.g ajax call etc....
             var v = $("#in").val();
             $("#out").html(v);
        }, timeout);
    }
});

полный пример здесь: http://jsfiddle.net/ZYXp4/8/


Это сработало для меня, хотя имейте в виду, что когда пользователь выходит из текстового поля, вы не получите событие keyup, так как к этому моменту оно потеряло фокус. Вместо этого установка его на keydown решает проблему.
horatius83

12

Оба топ-2 ответа не работают для меня. Итак, вот мое решение:

var timeout = null;

$('#myInput').keyup(function() {
    clearTimeout(timeout);

    timeout = setTimeout(function() {
        //do stuff here
    }, 500);
});

11

Мне нравится ответ Surreal Dream, но я обнаружил, что моя функция doneTyping будет срабатывать при каждом нажатии клавиши, т. Е. Если вы действительно быстро наберете «Hello»; вместо того, чтобы запускать один раз, когда вы прекращаете печатать, функция срабатывает 5 раз.

Проблема заключалась в том, что функция javascript setTimeout, по-видимому, не перезаписывает и не уничтожает все старые установленные таймауты, но если вы сделаете это самостоятельно, это сработает! Поэтому я просто добавил вызов clearTimeout непосредственно перед setTimeout, если установлен typingTimer. См. ниже:

//setup before functions
var typingTimer;                //timer identifier
var doneTypingInterval = 5000;  //time in ms, 5 second for example

//on keyup, start the countdown
$('#myInput').on("keyup", function(){
    if (typingTimer) clearTimeout(typingTimer);                 // Clear if already set     
    typingTimer = setTimeout(doneTyping, doneTypingInterval);
});

//on keydown, clear the countdown 
$('#myInput').on("keydown", function(){
    clearTimeout(typingTimer);
});

//user is "finished typing," do something
function doneTyping () {
    //do something
}

NB. Мне бы хотелось добавить это как комментарий к ответу Surreal Dream, но я новый пользователь и мне не хватает репутации. Сожалею!


10

Изменение принятого ответа для обработки дополнительных случаев, таких как вставка:

//setup before functions
var typingTimer;                //timer identifier
var doneTypingInterval = 2000;  //time in ms, 2 second for example
var $input = $('#myInput');

// updated events 
$input.on('input propertychange paste', function () {
    clearTimeout(typingTimer);
    typingTimer = setTimeout(doneTyping, doneTypingInterval);      
});

//user is "finished typing," do something
function doneTyping () {
  //do something
}

6

Я не думаю, что событие keyDown необходимо в этом случае (пожалуйста, скажите мне, почему, если я ошибаюсь). В моем (не jquery) скрипте подобное решение выглядит так:

var _timer, _timeOut = 2000; 



function _onKeyUp(e) {
    clearTimeout(_timer);
    if (e.keyCode == 13) {      // close on ENTER key
        _onCloseClick();
    } else {                    // send xhr requests
        _timer = window.setTimeout(function() {
            _onInputChange();
        }, _timeOut)
    }

}

Это мой первый ответ о переполнении стека, так что я надеюсь, что когда-нибудь это кому-нибудь поможет :)


6

Объявите следующую delayфункцию:

var delay = (function () {
    var timer = 0;
    return function (callback, ms) {
        clearTimeout(timer);
        timer = setTimeout(callback, ms);
    };
})()

и затем используйте это:

let $filter = $('#item-filter');
$filter.on('keydown', function () {
    delay(function () {            
        console.log('this will hit, once user has not typed for 1 second');            
    }, 1000);
});    

Ваше решение фантастическое, братан!
Разработчик

1
@Developer Большое спасибо. Я также добавил еще один ответ, чтобы решить немного более сложный сценарий (несколько элементов управления на странице).
Фабиан Биглер

хорошее решение Я использую это, но вместо keydown я использую событие 'input'
Budyn

С Keyup Event это отлично сработало для меня. Спасибо.
Синан Элдем

6

Поздний ответ, но я добавляю его, потому что сейчас 2019 год, и это вполне достижимо при использовании довольно ES6, без сторонних библиотек, и я считаю, что большинство высоко оцененных ответов громоздки и отягощены слишком большим количеством переменных.

Элегантное решение взято из этого отличного поста в блоге.

function debounce(callback, wait) {
  let timeout;
  return (...args) => {
      const context = this;
      clearTimeout(timeout);
      timeout = setTimeout(() => callback.apply(context, args), wait);
  };
}

window.addEventListener('keyup', debounce( () => {
    // code you would like to run 1000ms after the keyup event has stopped firing
    // further keyup events reset the timer, as expected
}, 1000))

1
Это действительно должен быть лучший ответ за год
Темный Бегемот

4

Ну, строго говоря, нет, так как компьютер не может угадать, когда пользователь закончил печатать. Конечно, вы можете запустить таймер при нажатии клавиши up и сбросить его при каждом последующем нажатии клавиши. Если таймер истекает, пользователь не набрал длительность таймера - это можно назвать «законченным набором».

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

(Если, конечно, вы не можете сказать по данным, когда они сделаны)


3

Я чувствую, что решение несколько проще с inputсобытием:

var typingTimer;
var doneTypingInterval = 500;

$("#myInput").on("input", function () {
    window.clearTimeout(typingTimer);
    typingTimer = window.setTimeout(doneTyping, doneTypingInterval);
});

function doneTyping () {
    // code here
}

3

согласен с ответом @going. Другое похожее решение, которое сработало для меня - это приведенное ниже. Единственное отличие состоит в том, что я использую .on ("input" ...) вместо keyup. Это только фиксирует изменения на входе. другие клавиши, такие как Ctrl, Shift и т. д. игнорируются

var typingTimer;                //timer identifier
var doneTypingInterval = 5000;  //time in ms (5 seconds)

//on input change, start the countdown

$('#myInput').on("input", function() {    
    clearTimeout(typingTimer);
    typingTimer = setTimeout(function(){
        // doSomething...
    }, doneTypingInterval);
});

3

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

шаг 1. установите тайм-аут в ноль, а затем очистите текущий тайм-аут, когда пользователь печатает.

шаг 2.trigger очистить тайм-аут для определения переменной до того, как будет запущено событие keyup.

шаг 3. определить время ожидания для переменной, объявленной выше;

<input type="text" id="input" placeholder="please type" style="padding-left:20px;"/>
<div class="data"></div>

код JavaScript

var textInput = document.getElementById('input');
var textdata = document.querySelector('.data');
// Init a timeout variable to be used below
var timefired = null;

// Listen for keystroke events
// Init a timeout variable to be used below
var timefired = null;// Listen for keystroke events
textInput.onkeyup = function (event) {
clearTimeout(timefired);
timefired = setTimeout(function () {
    textdata.innerHTML = 'Input Value:'+ textInput.value;
  }, 600);
};

1
на вопрос уже дан ответ ... и он очень похож на это
Mixone

3

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

После некоторой работы я применил метод проверки связи с сервером, когда пользователь перестает печатать.

Это решение сработало для меня:

$(document).ready(function() {
    $('#yourtextfield').keyup(function() {
        s = $('#yourtextfield').val();
        setTimeout(function() { 
            if($('#yourtextfield').val() == s){ // Check the value searched is the latest one or not. This will help in making the ajax call work when client stops writing.
                $.ajax({
                    type: "POST",
                    url: "yoururl",
                    data: 'search=' + s,
                    cache: false,
                    beforeSend: function() {
                       // loading image
                    },
                    success: function(data) {
                        // Your response will come here
                    }
                })
            }
        }, 1000); // 1 sec delay to check.
    }); // End of  keyup function
}); // End of document.ready

Вы заметите, что нет необходимости использовать какой-либо таймер при реализации этого.


2

Это простой код JS, который я написал:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="pt-br" lang="pt-br">
<head><title>Submit after typing finished</title>
<script language="javascript" type="text/javascript">
function DelayedSubmission() {
    var date = new Date();
    initial_time = date.getTime();
    if (typeof setInverval_Variable == 'undefined') {
            setInverval_Variable = setInterval(DelayedSubmission_Check, 50);
    } 
}
function DelayedSubmission_Check() {
    var date = new Date();
    check_time = date.getTime();
    var limit_ms=check_time-initial_time;
    if (limit_ms > 800) { //Change value in milliseconds
        alert("insert your function"); //Insert your function
        clearInterval(setInverval_Variable);
        delete setInverval_Variable;
    }
}

</script>
</head>
<body>

<input type="search" onkeyup="DelayedSubmission()" id="field_id" style="WIDTH: 100px; HEIGHT: 25px;" />

</body>
</html>

1

Вы можете использовать событие onblur, чтобы определить, когда текстовое поле теряет фокус: https://developer.mozilla.org/en/DOM/element.onblur

Это не то же самое, что «перестать печатать», если вас волнует случай, когда пользователь набирает кучу материала, а затем сидит там с текстовым полем все еще в фокусе.

Для этого я бы предложил связать setTimeout с событием onclick и предположить, что через x промежуток времени без нажатия клавиш пользователь перестал печатать.


1

Почему бы просто не использовать onfocusout?

https://www.w3schools.com/jsreF/event_onfocusout.asp

Если это форма, они всегда будут покидать фокус каждого поля ввода, чтобы нажимать кнопку отправки, чтобы вы знали, что при вводе обработчика события onfocusout никто не упустит ввод.


1
Когда задали вопрос, onfocusout оказал слабую поддержку (почти 7 лет назад), и эффект от onfocusout будет несколько иным. Вам нужно будет подождать, пока пользователь не оставит фокус на элементе, тогда как в случае решения по тайм-ауту / отладке он «срабатывает», как только пользователь перестает печатать, и пользователю не требуется переключать фокус. Примером использования может быть одна из тех регистрационных форм, где, когда пользователь прекращает вводить потенциальное имя пользователя, справа от поля формы появляется галочка или «X», указывающие, что имя доступно.
Давид Зоричта

если панель поиска находится в фокусе, никому не нужно выходить на улицу, через некоторое время результат будет отображаться напрямую, поскольку больше не печатается ни один открытый текст
P Satish Patro

1

Если пользователю необходимо отойти от поля, мы можем использовать «onBlur» вместо Onchange в Javascript

  <TextField id="outlined-basic"  variant="outlined" defaultValue={CardValue} onBlur={cardTitleFn} />

Если это не является необходимым, настройка таймера будет хорошим вариантом.


0

Как только вы обнаружите фокус на текстовом поле, при включении ключа выполните проверку тайм-аута и сбрасывайте его каждый раз, когда срабатывает.

Когда истечет время ожидания, сделайте запрос ajax.


0

Если вы ищете конкретную длину (например, поле почтового индекса):

$("input").live("keyup", function( event ){
if(this.value.length == this.getAttribute('maxlength')) {
        //make ajax request here after.
    }
  });

Неплохая идея сделать ограниченную проверку перед отправкой ajax-запроса.
Чим

0

Не уверен, что мои потребности просто странные, но мне нужно что-то похожее на это, и вот что я в итоге использовал:

$('input.update').bind('sync', function() {
    clearTimeout($(this).data('timer'));            
    $.post($(this).attr('data-url'), {value: $(this).val()}, function(x) {
        if(x.success != true) {
            triggerError(x.message);    
        }
    }, 'json');
}).keyup(function() {
    clearTimeout($(this).data('timer'));
    var val = $.trim($(this).val());
    if(val) {
        var $this = $(this);
        var timer = setTimeout(function() {
            $this.trigger('sync');
        }, 2000);
        $(this).data('timer', timer);
    }
}).blur(function() {
    clearTimeout($(this).data('timer'));     
    $(this).trigger('sync');
});

Что позволяет мне иметь такие элементы в моем приложении:

<input type="text" data-url="/controller/action/" class="update">

Которые обновляются, когда пользователь «закончил печатать» (без действия в течение 2 секунд) или перешел в другое поле (размывается из элемента)


0

Если вам нужно подождать, пока пользователь закончит ввод, используйте просто это:

$(document).on('change','#PageSize', function () {
    //Do something after new value in #PageSize       
});

Полный пример с вызовом ajax - это работает для моего пейджера - количество элементов в списке:

$(document).ready(function () {
    $(document).on('change','#PageSize', function (e) {
        e.preventDefault();
        var page = 1;
        var pagesize = $("#PageSize").val();
        var q = $("#q").val();
        $.ajax({
            url: '@Url.Action("IndexAjax", "Materials", new { Area = "TenantManage" })',
            data: { q: q, pagesize: pagesize, page: page },
            type: 'post',
            datatype: "json",
            success: function (data) {
                $('#tablecontainer').html(data);
               // toastr.success('Pager has been changed', "Success!");
            },
            error: function (jqXHR, exception) {
                ShowErrorMessage(jqXHR, exception);
            }
        });  
    });
});    

0

Несколько таймеров на страницу

Все остальные ответы работают только для одного элемента управления ( мой другой ответ включен). Если у вас есть несколько элементов управления на странице (например, в корзине), будет вызван только последний элемент управления, где пользователь что-то набрал . В моем случае это, конечно, не желаемое поведение - у каждого элемента управления должен быть свой таймер.

Чтобы решить эту проблему, вам просто нужно передать идентификатор функции и поддерживать словарь timeoutHandles, как показано в следующем коде:

Объявление функции:

var delayUserInput = (function () {
    var timeoutHandles = {};    
    return function (id, callback, ms) {        
        if (timeoutHandles[id]) {
            clearTimeout(timeoutHandles[id]);
        }

        timeoutHandles[id] = setTimeout(callback, ms);             
    };
})();

Использование функции:

  delayUserInput('yourID', function () {
     //do some stuff
  }, 1000);

0

Просто и легко понять.

var mySearchTimeout;
$('#ctl00_mainContent_CaseSearch').keyup(function () {
   clearTimeout(mySearchTimeout);
   var filter = $(this).val();
   mySearchTimeout = setTimeout(function () { myAjaxCall(filter); }, 700);
   return true;
});

0

Для передачи параметров в вашу функцию вместе с синтаксисом ES6.

$(document).ready(() => {
    let timer = null;
     $('.classSelector').keydown(() => {
     clearTimeout(timer); 
     timer = setTimeout(() => foo('params'), 500);
  });
});

const foo = (params) => {
  console.log(`In foo ${params}`);
}

-2

Вау, даже 3 комментария довольно правильные!

  1. Пустой ввод не является причиной пропуска вызова функции, например, я удаляю ненужный параметр из URL перед перенаправлением

  2. .on ('input', function() { ... });должны быть использованы для запуска keyup, pasteи changeсобытия

  3. определенно .val()или .valueдолжны быть использованы

  4. Вы можете использовать $(this)функцию внутри события вместо того, #idчтобы работать с несколькими входами

  5. (мое решение) Я использую анонимные функции , а не doneTypingв setTimeoutлегко получать доступ $(this)из n.4, но вы должны сохранить его первый какvar $currentInput = $(this);

РЕДАКТИРОВАТЬ Я вижу, что некоторые люди не понимают направления без возможности скопировать и вставить готовый код. Вот ты где

var typingTimer;
//                  2
$("#myinput").on('input', function () {
    //             4     3
    var input = $(this).val();
    clearTimeout(typingTimer);
    //                           5
    typingTimer = setTimeout(function() {
        // do something with input
        alert(input);
    }, 5000);      
});

Как это отвечает на вопрос?
Томас Орлита

@ThomasOrlita этот ответ дополняет 3 наиболее рейтинговых ответа. Взгляните на принятый: 1. он не поддерживает paste, не использует thisи т. Д. (Я думаю, что это важно, но я не хочу потеряться в тоннах комментариев)
vladkras

1
Это не отвечает на вопрос. Если вы хотите прокомментировать другой ответ, есть система комментирования (я использую ее прямо сейчас!)
GrayedFox
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.