UNPKG

derby

Version:

MVC framework making it easy to write realtime, collaborative applications that run in both Node.js and browsers.

114 lines (113 loc) 4.25 kB
var textDiff = require('./textDiff'); exports.add = addDocumentListeners; exports.inputSupportsSelection = inputSupportsSelection; // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#do-not-apply // TODO: Date types support function inputSupportsSelection(input) { var type = input.type; return (type === 'text' || type === 'textarea' || type === 'search' || type === 'url' || type === 'tel' || type === 'password'); } function inputIsNumberValue(input) { var type = input.type; return (type === 'number' || (type === 'range' && !input.multiple)); } var inputValue = function (input) { return inputIsNumberValue(input) ? input.valueAsNumber : input.value; }; function addDocumentListeners(doc) { doc.addEventListener('input', documentInput, true); doc.addEventListener('change', documentChange, true); // Listen to more events for versions of IE with buggy input event implementations if (parseFloat(window.navigator.appVersion.split('MSIE ')[1]) <= 9) { // We're listening on selectionchange because there's no other event emitted when // the user clicks 'delete' from a context menu when right clicking on selected text. // So although this event fires overly aggressively, it's the only real way // to ensure that we can detect all changes to the input value in IE <= 9 doc.addEventListener('selectionchange', function () { if (document.activeElement) { documentInput({ target: document.activeElement }); // selectionchange evts don't have the e.target we need } }, true); } // For some reason valueAsNumber returns NaN for number inputs in IE // until a new IE version that handles this is released, parse input.value as a fallback var input = document.createElement('input'); input.type = 'number'; input.value = '7'; if (input.valueAsNumber !== input.valueAsNumber) { var oldInputValue = inputValue; inputValue = function (input) { if (input.type === 'number') { return inputIsNumberValue(input) ? parseFloat(input.value) : input.value; } else { return oldInputValue.apply(this, arguments); } }; } } function documentInput(e) { var target = e.target; if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') { setInputValue(e, target); } } function documentChange(e) { var target = e.target; if (target.tagName === 'INPUT') { setBoundProperty(target, 'checked'); setInputValue(e, target); } else if (target.tagName === 'SELECT') { setOptionBindings(target); } else if (target.tagName === 'TEXTAREA') { setInputValue(e, target); } } function setBoundProperty(node, property) { var binding = node.$bindAttributes && node.$bindAttributes[property]; if (!binding || binding.isUnbound()) return; var value = node[property]; binding.template.expression.set(binding.context, value); } function setInputValue(e, target) { var binding = target.$bindAttributes && target.$bindAttributes.value; if (!binding || binding.isUnbound()) return; if (inputSupportsSelection(target)) { var pass = { $event: e }; textDiffBinding(binding, target.value, pass); } else { var value = inputValue(target); binding.template.expression.set(binding.context, value); } } function textDiffBinding(binding, value, pass) { var expression = binding.template.expression; var segments = expression.pathSegments(binding.context); if (segments) { var model = binding.context.controller.model.pass(pass); textDiff.onTextInput(model, segments, value); } else if (expression.set) { expression.set(binding.context, value); } } function setOptionBindings(parent) { for (var node = parent.firstChild; node; node = node.nextSibling) { if (node.tagName === 'OPTION') { setBoundProperty(node, 'selected'); } else if (node.hasChildNodes()) { setOptionBindings(node); } } }