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

Триггерное действие при изменении программы на входное значение

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

Я пробовал много комбинаций с помощью 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, заключается в добавлении второго ввода для взаимодействия с пользователем.

4b9b3361

Ответ 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">