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
JavaScript
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);
}
}
}