Триггерное действие при изменении программы на входное значение
Моя цель - наблюдать входное значение и запускать обработчик, когда его значение изменяется программно. Мне это нужно только для современных браузеров.
Я пробовал много комбинаций с помощью defineProperty
, и это моя последняя итерация:
var myInput=document.getElementById("myInput");
Object.defineProperty(myInput,"value",{
get:function(){
return this.getAttribute("value");
},
set:function(val){
console.log("set");
// handle value change here
this.setAttribute("value",val);
}
});
myInput.value="new value"; // should trigger console.log and handler
Это похоже на то, что я ожидаю, но это похоже на хак, поскольку я переопределяю существующее свойство value и играю с двойным статусом value
(атрибут и свойство). Он также разбивает событие change
, которое не похоже на измененное свойство.
Другие мои попытки:
- цикл setTimeout/setInterval, но это не чисто.
- различные
watch
и observe
polyfills, но они ломаются для свойства входного значения
Каким будет правильный способ добиться того же результата?
Live demo: http://jsfiddle.net/L7Emx/4/
[Изменить] Чтобы уточнить: мой код просматривает элемент ввода, в котором другие приложения могут нажимать обновления (например, в результате вызовов ajax, или в результате изменений в других полях). Я не контролирую, как другие приложения нажимают обновления, я просто наблюдатель.
[Изменить 2] Чтобы уточнить, что я имею в виду под "современным браузером", я был бы очень доволен решением, которое работает на IE 11 и Chrome 30.
[Обновить] Обновлено демо на основе принятого ответа: http://jsfiddle.net/L7Emx/10/
Фокус, предложенный @mohit-jain, заключается в добавлении второго ввода для взаимодействия с пользователем.
Ответы
Ответ 1
если единственная проблема с вашим решением - нарушение события изменения на установленном значении. вы можете запустить это событие вручную по набору. (Но этот монитор не будет установлен в случае, если пользователь внесет изменения во вход через браузер - см. изменить ниже)
<html>
<body>
<input type='hidden' id='myInput' />
<input type='text' id='myInputVisible' />
<input type='button' value='Test' onclick='return testSet();'/>
<script>
//hidden input which your API will be changing
var myInput=document.getElementById("myInput");
//visible input for the users
var myInputVisible=document.getElementById("myInputVisible");
//property mutation for hidden input
Object.defineProperty(myInput,"value",{
get:function(){
return this.getAttribute("value");
},
set:function(val){
console.log("set");
//update value of myInputVisible on myInput set
myInputVisible.value = val;
// handle value change here
this.setAttribute("value",val);
//fire the event
if ("createEvent" in document) { // Modern browsers
var evt = document.createEvent("HTMLEvents");
evt.initEvent("change", true, false);
myInput.dispatchEvent(evt);
}
else { // IE 8 and below
var evt = document.createEventObject();
myInput.fireEvent("onchange", evt);
}
}
});
//listen for visible input changes and update hidden
myInputVisible.onchange = function(e){
myInput.value = myInputVisible.value;
};
//this is whatever custom event handler you wish to use
//it will catch both the programmatic changes (done on myInput directly)
//and user changes (done on myInputVisible)
myInput.onchange = function(e){
console.log(myInput.value);
};
//test method to demonstrate programmatic changes
function testSet(){
myInput.value=Math.floor((Math.random()*100000)+1);
}
</script>
</body>
</html>
подробнее о стрельбе вручную
ИЗМЕНИТЬ
Проблема с ручным запуском события и мутаторным подходом заключается в том, что свойство value не изменится, когда пользователь изменит значение поля из браузера. работа вокруг состоит в том, чтобы использовать два поля. один скрытый, с которым мы можем иметь программное взаимодействие. Другой вид, с которым пользователь может взаимодействовать. После этого подход рассмотрения достаточно прост.
- изменить значение свойства на скрытое поле ввода, чтобы наблюдать за изменениями и запускать ручное событие onchange. на заданное значение измените значение видимого поля, чтобы дать пользователю обратную связь.
- при изменении значения видимого поля обновляет значение скрытого для наблюдателя.
Ответ 2
Следующие работы повсюду я пробовал, включая IE11 (вплоть до режима эмуляции IE9).
Это займет немного больше defineProperty
, найдя объект в цепочке прототипов элемента ввода, который определяет установщик .value
и модифицирует этот сеттер для запуска события (я назвал его modified
в примере), сохраняя при этом прежнее поведение.
Когда вы запустите фрагмент ниже, вы можете ввести/вставить/еще что-то в текстовое поле ввода или нажать кнопку, добавляющую " more"
к элементу ввода .value
. В любом случае содержимое <span>
синхронно обновляется.
Единственное, что не обрабатывается здесь, - это обновление, вызванное установкой атрибута. Вы можете обработать это с помощью MutationObserver
, если хотите, но обратите внимание, что между .value
и атрибутом value
нет взаимно-однозначного отношения (последнее является только значением по умолчанию для первого).
// make all input elements trigger an event when programmatically setting .value
monkeyPatchAllTheThings();
var input = document.querySelector("input");
var span = document.querySelector("span");
function updateSpan() {
span.textContent = input.value;
}
// handle user-initiated changes to the value
input.addEventListener("input", updateSpan);
// handle programmatic changes to the value
input.addEventListener("modified", updateSpan);
// handle initial content
updateSpan();
document.querySelector("button").addEventListener("click", function () {
input.value += " more";
});
function monkeyPatchAllTheThings() {
// create an input element
var inp = document.createElement("input");
// walk up its prototype chain until we find the object on which .value is defined
var valuePropObj = Object.getPrototypeOf(inp);
var descriptor;
while (valuePropObj && !descriptor) {
descriptor = Object.getOwnPropertyDescriptor(valuePropObj, "value");
if (!descriptor)
valuePropObj = Object.getPrototypeOf(valuePropObj);
}
if (!descriptor) {
console.log("couldn't find .value anywhere in the prototype chain :(");
} else {
console.log(".value descriptor found on", "" + valuePropObj);
}
// remember the original .value setter ...
var oldSetter = descriptor.set;
// ... and replace it with a new one that a) calls the original,
// and b) triggers a custom event
descriptor.set = function () {
oldSetter.apply(this, arguments);
// for simplicity I'm using the old IE-compatible way of creating events
var evt = document.createEvent("Event");
evt.initEvent("modified", true, true);
this.dispatchEvent(evt);
};
// re-apply the modified descriptor
Object.defineProperty(valuePropObj, "value", descriptor);
}
<input><br><br>
The input contains "<span></span>"<br><br>
<button>update input programmatically</button>
Ответ 3
Поскольку вы уже используете полисы для просмотра/наблюдения и т.д., позвольте мне воспользоваться возможностью, чтобы предложить вам Angularjs.
Он предлагает именно эту функциональность в виде ng-моделей. Вы можете поместить наблюдателей на значение модели, а когда оно изменится, вы можете вызвать другие функции.
Вот очень простое, но работающее решение для того, что вы хотите:
http://jsfiddle.net/RedDevil/jv8pK/
В принципе, сделайте ввод текста и привяжите его к модели:
<input type="text" data-ng-model="variable">
затем поместите наблюдателя на модель углов на этом входе в контроллер.
$scope.$watch(function() {
return $scope.variable
}, function(newVal, oldVal) {
if(newVal !== null) {
window.alert('programmatically changed');
}
});
Ответ 4
Мне это нужно только для современных браузеров.
Как современно вы хотели бы пойти? Ecma Script 7 (6 будет завершено в декабре) может содержать Object.observe. Это позволит создавать собственные наблюдаемые. И да, вы можете запустить его! Как?
Чтобы поэкспериментировать с этой функцией, вам необходимо включить Enable Экспериментальный флагом JavaScript в Chrome Canary и перезапустите браузер. Флаг можно найти под 'about:flags’
Дополнительная информация: прочитать это.
Итак, да, это очень экспериментально и не готово в текущем наборе браузеров. Кроме того, он все еще не полностью готов, а не 100%, если он подходит к ES7, а конечная дата для ES7 еще не установлена. Тем не менее, я хотел сообщить вам о будущем.
Ответ 5
Есть способ сделать это.
Для этого нет события DOM, однако есть событие javascript, которое запускает изменение свойства объекта.
document.form1.textfield.watch("value", function(object, oldval, newval){})
^ Object watched ^ ^ ^
|_ property watched | |
|________|____ old and new value
В обратном вызове вы можете делать что угодно.
В этом примере мы можем увидеть этот эффект (проверьте jsFiddle):
var obj = { prop: 123 };
obj.watch('prop', function(propertyName, oldValue, newValue){
console.log('Old value is '+oldValue); // 123
console.log('New value is '+newValue); // 456
});
obj.prop = 456;
Когда obj
изменяется, он активирует прослушиватель watch
.
У вас есть дополнительная информация по этой ссылке: http://james.padolsey.com/javascript/monitoring-dom-properties/
Ответ 6
Недавно я написал следующее Gist, которое позволяет прослушивать межсетевой браузер настраиваемых событий (включая IE8 +).
Посмотрите, как я слушаю onpropertychange
в IE8.
util.listenToCustomEvents = function (event_name, callback) {
if (document.addEventListener) {
document.addEventListener(event_name, callback, false);
} else {
document.documentElement.attachEvent('onpropertychange', function (e) {
if(e.propertyName == event_name) {
callback();
}
}
};
Я не уверен, что решение IE8 работает с перекрестным браузером, но вы можете установить фальшивый eventlistener
в свойстве value
вашего ввода и запустить обратный вызов после изменения значений value
, вызванных onpropertychange
.
Ответ 7
Это старый вопрос, но с новым объектом JS Proxy вызвать событие при изменении значения довольно просто:
let proxyInput = new Proxy(input, {
set(obj, prop, value) {
obj[prop] = value;
if(prop === 'value'){
let event = new InputEvent('input', {data: value})
obj.dispatchEvent(event);
}
return true;
}
})
input.addEventListener('input', $event => {
output.value = 'Input changed, new value: ${$event.data}';
});
proxyInput.value = 'thing'
window.setTimeout(() => proxyInput.value = 'another thing', 1500);
<input id="input">
<output id="output">